Compare commits
217 Commits
memofs-v0.
...
v7.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07637dfe96 | ||
|
|
f389a4a1db | ||
|
|
af077c796c | ||
|
|
1c319f2fa8 | ||
|
|
e8afa03f7b | ||
|
|
9b22545842 | ||
|
|
adc733d25c | ||
|
|
6896257647 | ||
|
|
1d9845a6cb | ||
|
|
8461339e9a | ||
|
|
9904d94e4c | ||
|
|
da25c80d0b | ||
|
|
5fa63733fd | ||
|
|
8b54bf0ba1 | ||
|
|
173dc12cb3 | ||
|
|
e136529ff0 | ||
|
|
75542dacb3 | ||
|
|
07abfbde43 | ||
|
|
96112fe118 | ||
|
|
9d0b313261 | ||
|
|
277ddfa9be | ||
|
|
5d88bdb256 | ||
|
|
8d29b43155 | ||
|
|
cc071a6415 | ||
|
|
8954def25c | ||
|
|
d484098781 | ||
|
|
9f06cbf3a0 | ||
|
|
af4a3ca0af | ||
|
|
43715143e4 | ||
|
|
16aa354d36 | ||
|
|
c739025453 | ||
|
|
f0526d17de | ||
|
|
6cc2e919c0 | ||
|
|
e1f9eaefa9 | ||
|
|
5d62bf9b60 | ||
|
|
4aa5814a0a | ||
|
|
5bca244062 | ||
|
|
7cf57714a4 | ||
|
|
92e6f862ad | ||
|
|
2377f41036 | ||
|
|
26a08f4d9f | ||
|
|
672d207961 | ||
|
|
a3d8e50f26 | ||
|
|
d3abca46a8 | ||
|
|
17fdd18c55 | ||
|
|
e482d038c6 | ||
|
|
d0482a004e | ||
|
|
561a3e3256 | ||
|
|
158dac5e1c | ||
|
|
1413f8c0b6 | ||
|
|
ffb2aa332a | ||
|
|
45e8208e9c | ||
|
|
7f230a8bf4 | ||
|
|
afe26b8c16 | ||
|
|
d153f62b8a | ||
|
|
5c80cd6e50 | ||
|
|
df1aced95d | ||
|
|
5a0a8f5077 | ||
|
|
1c281539e0 | ||
|
|
ef41d14f50 | ||
|
|
aa29397732 | ||
|
|
ca8865a3ce | ||
|
|
7599ef6626 | ||
|
|
43e71f9242 | ||
|
|
aac9b25efe | ||
|
|
532d170585 | ||
|
|
3dcb14013b | ||
|
|
a4c782cd35 | ||
|
|
0779baa0ac | ||
|
|
0849fab644 | ||
|
|
d85bca2e7e | ||
|
|
c7ab6c435c | ||
|
|
98db3b4f08 | ||
|
|
0e7ba839ed | ||
|
|
2c27691e57 | ||
|
|
934506bdfd | ||
|
|
f313fa4ae1 | ||
|
|
78755b6130 | ||
|
|
8bf59e5cbb | ||
|
|
8f71d13714 | ||
|
|
f4a790eb50 | ||
|
|
0d951c8ad1 | ||
|
|
59ef5f05ea | ||
|
|
b84aab0960 | ||
|
|
0e89b91c38 | ||
|
|
7888a704e1 | ||
|
|
804fd3de8e | ||
|
|
4992c36f08 | ||
|
|
27af0c841b | ||
|
|
cc4f4df4f9 | ||
|
|
5989ab3b85 | ||
|
|
040b9c1452 | ||
|
|
bb7bd2e27e | ||
|
|
02dbd4ba75 | ||
|
|
3b149cc875 | ||
|
|
f3745c68d2 | ||
|
|
eddbe7d0cf | ||
|
|
faf86d006a | ||
|
|
71e4dfeb14 | ||
|
|
da8ed6ddf9 | ||
|
|
503e687c55 | ||
|
|
dd667cce0b | ||
|
|
f911009752 | ||
|
|
cae4c46669 | ||
|
|
a937fc38db | ||
|
|
ff43ffce07 | ||
|
|
5bb3dc258a | ||
|
|
98238e4516 | ||
|
|
23b8308282 | ||
|
|
a1f7cdc2b6 | ||
|
|
dcb30351c5 | ||
|
|
1c0dc60071 | ||
|
|
0401c3ac0e | ||
|
|
3b800d1cd7 | ||
|
|
35efb464e5 | ||
|
|
19a955a327 | ||
|
|
836b18e68a | ||
|
|
046dc0d598 | ||
|
|
039d92ce78 | ||
|
|
2136da15d6 | ||
|
|
e5041d80ef | ||
|
|
f66860bdfe | ||
|
|
50f0a2bd2e | ||
|
|
7cd9bd383e | ||
|
|
45a20a1633 | ||
|
|
ec5b3f80ef | ||
|
|
3b257ea87a | ||
|
|
6b82cead9c | ||
|
|
79ae4c52cd | ||
|
|
a4616cda7d | ||
|
|
95648361be | ||
|
|
0c41e9c10b | ||
|
|
61c7ef3cb0 | ||
|
|
65898125d0 | ||
|
|
da05078ff3 | ||
|
|
badb5c3636 | ||
|
|
9453588ab1 | ||
|
|
4cbb3874a4 | ||
|
|
940aff7ef4 | ||
|
|
a3edb93273 | ||
|
|
782b054b1a | ||
|
|
fc27b2911e | ||
|
|
486b067567 | ||
|
|
bdd1afea57 | ||
|
|
5ccd02939b | ||
|
|
ca5b8ab309 | ||
|
|
9481fdd38d | ||
|
|
56bf6d282b | ||
|
|
5364c9c1bc | ||
|
|
a4d4beeb97 | ||
|
|
30a01381be | ||
|
|
def99a9e4d | ||
|
|
1214fc8b0d | ||
|
|
5a5b1268d3 | ||
|
|
6a1fffd1ce | ||
|
|
571ef3060a | ||
|
|
3cf82e112f | ||
|
|
9b459c20d6 | ||
|
|
5c85cd27c3 | ||
|
|
4bf73c7a8a | ||
|
|
62e51b7535 | ||
|
|
729a7f0053 | ||
|
|
03c297190d | ||
|
|
9c790eddd7 | ||
|
|
8ebe7e332b | ||
|
|
f43777e37e | ||
|
|
691a8fcdeb | ||
|
|
69c0e8d70e | ||
|
|
330c92c9a8 | ||
|
|
cf0ff60d31 | ||
|
|
9e9cf5dd1f | ||
|
|
5768d8e4a4 | ||
|
|
3b433e53be | ||
|
|
28ddf40344 | ||
|
|
c1286db9c1 | ||
|
|
f13940262e | ||
|
|
9f0a6101b8 | ||
|
|
0b0fe01a7c | ||
|
|
85e098d5c8 | ||
|
|
e8d1faf4e2 | ||
|
|
2a46df1110 | ||
|
|
1601e6d26e | ||
|
|
0e4f6dea2b | ||
|
|
a2356773dc | ||
|
|
4a4da4737d | ||
|
|
2cefd1bf2e | ||
|
|
c5ce15fe34 | ||
|
|
76dea568c9 | ||
|
|
8e81140eff | ||
|
|
d58e1f0792 | ||
|
|
830c242751 | ||
|
|
91d45afd0f | ||
|
|
102c77b23e | ||
|
|
aa4039a2e7 | ||
|
|
c065ded440 | ||
|
|
f69096dadb | ||
|
|
363f95ba14 | ||
|
|
dcc15e8911 | ||
|
|
bd13047b58 | ||
|
|
1a83789c01 | ||
|
|
1cbe272e19 | ||
|
|
6de74b41b3 | ||
|
|
b0fc9ee507 | ||
|
|
a95ffe1d31 | ||
|
|
4119a510f5 | ||
|
|
cfa7f03815 | ||
|
|
9b4c89820d | ||
|
|
fe874720aa | ||
|
|
f7c0f33eb5 | ||
|
|
c1bf9d9dfc | ||
|
|
255bf439d3 | ||
|
|
2a31937b81 | ||
|
|
eb8964e1d1 | ||
|
|
fe0ca280a1 | ||
|
|
e8e3b7b985 | ||
|
|
c437507442 | ||
|
|
ca151b434e |
21
.github/workflows/ci.yml
vendored
@@ -1,6 +1,13 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -9,10 +16,12 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
rust_version: [stable, "1.40.0"]
|
||||
rust_version: [stable, "1.55.0"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: rustup default ${{ matrix.rust_version }}
|
||||
@@ -27,10 +36,4 @@ jobs:
|
||||
run: |
|
||||
cargo fmt -- --check
|
||||
cargo clippy
|
||||
if: matrix.rust_version == 'stable'
|
||||
|
||||
- name: Build (All Features)
|
||||
run: cargo build --locked --verbose --all-features
|
||||
|
||||
- name: Run tests (All Features)
|
||||
run: cargo test --locked --verbose --all-features
|
||||
if: matrix.rust_version == 'stable'
|
||||
6
.github/workflows/release.yml
vendored
@@ -10,6 +10,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Build release binary
|
||||
run: cargo build --verbose --locked --release
|
||||
@@ -25,6 +27,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust
|
||||
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
@@ -47,6 +51,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --locked --verbose --release
|
||||
|
||||
8
.gitignore
vendored
@@ -10,9 +10,15 @@
|
||||
/*.rbxl
|
||||
/*.rbxlx
|
||||
|
||||
# Test places for the Roblox Studio Plugin
|
||||
/plugin/*.rbxlx
|
||||
|
||||
# Roblox Studio holds 'lock' files on places
|
||||
*.rbxl.lock
|
||||
*.rbxlx.lock
|
||||
|
||||
# Snapshot files from the 'insta' Rust crate
|
||||
**/*.snap.new
|
||||
**/*.snap.new
|
||||
|
||||
# Selene generates a roblox.toml file that should not be checked in.
|
||||
/roblox.toml
|
||||
6
.gitmodules
vendored
@@ -10,6 +10,6 @@
|
||||
[submodule "plugin/modules/t"]
|
||||
path = plugin/modules/t
|
||||
url = https://github.com/osyrisrblx/t.git
|
||||
[submodule "plugin/modules/rbx-dom"]
|
||||
path = plugin/modules/rbx-dom
|
||||
url = http://github.com/rojo-rbx/rbx-dom
|
||||
[submodule "plugin/modules/flipper"]
|
||||
path = plugin/modules/flipper
|
||||
url = https://github.com/Reselim/Flipper
|
||||
|
||||
@@ -33,7 +33,7 @@ stds.plugin = {
|
||||
stds.testez = {
|
||||
read_globals = {
|
||||
"describe",
|
||||
"it", "itFOCUS", "itSKIP",
|
||||
"it", "itFOCUS", "itSKIP", "itFIXME",
|
||||
"FOCUS", "SKIP", "HACK_NO_XPCALL",
|
||||
"expect",
|
||||
}
|
||||
|
||||
186
CHANGELOG.md
@@ -1,8 +1,188 @@
|
||||
# Rojo Changelog
|
||||
|
||||
## Unreleased Changes for 0.6.x
|
||||
## Unreleased Changes
|
||||
|
||||
## [7.0.0] - December 10, 2021
|
||||
* Fixed Rojo's interactions with properties enabled by FFlags that are not yet enabled. ([#493])
|
||||
* Improved output in Roblox Studio plugin when bad property data is encountered.
|
||||
* Reintroduced support for CFrame shorthand syntax in Rojo project and `.meta.json` files, matching Rojo 6. ([#430])
|
||||
* Connection settings are now remembered when reconnecting in Roblox Studio. ([#500])
|
||||
* Updated reflection database to Roblox v503.
|
||||
|
||||
[#430]: https://github.com/rojo-rbx/rojo/issues/430
|
||||
[#493]: https://github.com/rojo-rbx/rojo/pull/493
|
||||
[#500]: https://github.com/rojo-rbx/rojo/pull/500
|
||||
[7.0.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0
|
||||
|
||||
## [7.0.0-rc.3] - October 19, 2021
|
||||
This is the last release candidate for Rojo 7. In an effort to get Rojo 7 out the door, we'll be freezing features from here on out, something we should've done a couple months ago.
|
||||
|
||||
Expect to see Rojo 7 stable soon!
|
||||
|
||||
* Added support for writing `Tags` in project files, model files, and meta files. ([#484])
|
||||
* Adjusted Studio plugin colors to match Roblox Studio palette. ([#482])
|
||||
* Improved experimental two-way sync feature by batching changes. ([#478])
|
||||
|
||||
[#482]: https://github.com/rojo-rbx/rojo/pull/482
|
||||
[#484]: https://github.com/rojo-rbx/rojo/pull/484
|
||||
[#478]: https://github.com/rojo-rbx/rojo/pull/478
|
||||
[7.0.0-rc.3]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-rc.3
|
||||
|
||||
## 7.0.0-rc.2 - October 19, 2021
|
||||
(Botched release due to Git mishap, oops!)
|
||||
|
||||
## [7.0.0-rc.1] - August 23, 2021
|
||||
In Rojo 6 and previous Rojo 7 alphas, an explicit Vector3 property would be written like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"className": "Part",
|
||||
"properties": {
|
||||
"Position": {
|
||||
"Type": "Vector3",
|
||||
"Value": [1, 2, 3]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For Rojo 7, this will need to be changed to:
|
||||
|
||||
```json
|
||||
{
|
||||
"className": "Part",
|
||||
"properties": {
|
||||
"Position": {
|
||||
"Vector3": [1, 2, 3]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The shorthand property format that most users use is not impacted. For reference, it looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"className": "Part",
|
||||
"properties": {
|
||||
"Position": [1, 2, 3]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Major breaking change: changed property syntax for project files; shorthand syntax is unchanged.
|
||||
* Added the `fmt-project` subcommand for formatting Rojo project files.
|
||||
* Improved error output for many subcommands.
|
||||
* Updated to stable versions of rbx-dom libraries.
|
||||
* Updated async infrastructure, which should fix a handful of bugs. ([#459])
|
||||
* Fixed syncing refs in the Roblox Studio plugin ([#462], [#466])
|
||||
* Added support for long paths on Windows. ([#464])
|
||||
|
||||
[#459]: https://github.com/rojo-rbx/rojo/pull/459
|
||||
[#462]: https://github.com/rojo-rbx/rojo/pull/462
|
||||
[#464]: https://github.com/rojo-rbx/rojo/pull/464
|
||||
[#466]: https://github.com/rojo-rbx/rojo/pull/466
|
||||
[7.0.0-rc.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-rc.1
|
||||
|
||||
## [7.0.0-alpha.4][7.0.0-alpha.4] (May 5, 2021)
|
||||
* Added the `gameId` and `placeId` optional properties to project files.
|
||||
* When connecting from the Rojo Roblox Studio plugin, Rojo will set the game and place ID of the current place to these values, if set.
|
||||
* This is equivalent to running `game:SetUniverseId(...)` and `game:SetPlaceId(...)` from the command bar in Studio.
|
||||
* Added "EXPERIMENTAL!" label to two-way sync toggle in Rojo's Roblox Studio plugin.
|
||||
* Fixed `Name` and `Parent` properties being allowed in Rojo projects. ([#413][pr-413])
|
||||
* Fixed "Open Scripts Externally" feature crashing Studio. ([#369][issue-369])
|
||||
* Empty `.model.json` files will no longer cause errors. ([#420][pr-420])
|
||||
* When specifying `$path` on a service, Rojo now keeps the correct class name. ([#331][issue-331])
|
||||
* Improved error messages for misconfigured projects.
|
||||
|
||||
[issue-331]: https://github.com/rojo-rbx/rojo/issues/331
|
||||
[issue-369]: https://github.com/rojo-rbx/rojo/issues/369
|
||||
[pr-420]: https://github.com/rojo-rbx/rojo/pull/420
|
||||
[pr-413]: https://github.com/rojo-rbx/rojo/pull/413
|
||||
[7.0.0-alpha.4]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.4
|
||||
|
||||
## [7.0.0-alpha.3][7.0.0-alpha.3] (February 19, 2021)
|
||||
* Updated dependencies, fixing `OptionalCoordinateFrame`-related issues.
|
||||
* Added `--address` flag to `rojo serve` to allow for external connections. ([#403][pr-403])
|
||||
|
||||
[pr-403]: https://github.com/rojo-rbx/rojo/pull/403
|
||||
[7.0.0-alpha.3]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.3
|
||||
|
||||
## [7.0.0-alpha.2][7.0.0-alpha.2] (February 19, 2021)
|
||||
* Fixed incorrect protocol version between the client and server.
|
||||
|
||||
[7.0.0-alpha.2]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.2
|
||||
|
||||
## [7.0.0-alpha.1][7.0.0-alpha.1] (February 18, 2021)
|
||||
This release includes a brand new implementation of the Roblox DOM. It brings performance improvements, much better support for `rbxl` and `rbxm` files, and a better internal API.
|
||||
|
||||
* Added support for all remaining property types.
|
||||
* Added support for the entire Roblox binary model format.
|
||||
* Changed `rojo upload` to upload binary places and models instead of XML.
|
||||
* This should make using `rojo upload` much more feasible for large places.
|
||||
* **Breaking**: Changed format of some types of values in `project.json`, `model.json`, and `meta.json` files.
|
||||
* This should impact few projects. See [this file][allValues.json] for new examples of each property type.
|
||||
|
||||
Formatting of types will change more before the stable release of Rojo 7. We're hoping to use this opportunity to normalize some of the case inconsistency introduced in Rojo 0.5.
|
||||
|
||||
[7.0.0-alpha.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.1
|
||||
[allValues.json]: https://github.com/rojo-rbx/rojo/blob/f4a790eb50b74e482000bad1dcfe22533992fb20/plugin/rbx_dom_lua/src/allValues.json
|
||||
|
||||
## [6.0.2](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.2) (February 9, 2021)
|
||||
* Fixed `rojo upload` to handle CSRF challenges.
|
||||
|
||||
## [6.0.1](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.1) (January 22, 2021)
|
||||
* Fixed `rojo upload` requests being rejected by Roblox
|
||||
|
||||
## [6.0.0](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.0) (January 16, 2021)
|
||||
* Improved server error messages
|
||||
* The server will now keep running in more error cases
|
||||
* Fixed Rojo being unable to diff ClassName changes
|
||||
|
||||
## [6.0.0 Release Candidate 4](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.0-rc.4) (December 14, 2020)
|
||||
* Added brand new Rojo UI ([#367](https://github.com/rojo-rbx/rojo/pull/367))
|
||||
* Added `projectName` to `/api/rojo` output.
|
||||
|
||||
## [6.0.0 Release Candidate 3](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.0-rc.3) (November 19, 2020)
|
||||
* Fixed the Rojo plugin attempting to write the non-scriptable properties `Instance.SourceAssetId` and `HttpServer.HttpEnabled`.
|
||||
* Fixed the Rojo plugin's handling of null referents.
|
||||
|
||||
## [6.0.0 Release Candidate 2](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.0-rc.2) (November 19, 2020)
|
||||
* Fixed crash when malformed CSV files are put into a project. ([#310](https://github.com/rojo-rbx/rojo/issues/310))
|
||||
* Fixed incorrect string escaping when producing Lua code from JSON files. ([#314](https://github.com/rojo-rbx/rojo/issues/314))
|
||||
* Fixed performance issues introduced in Rojo 6.0.0-rc.1. ([#317](https://github.com/rojo-rbx/rojo/issues/317))
|
||||
* Fixed `rojo plugin install` subcommand failing for everyone except Rojo developers. ([#320](https://github.com/rojo-rbx/rojo/issues/320))
|
||||
* Updated default place template to take advantage of [#210](https://github.com/rojo-rbx/rojo/pull/210).
|
||||
* Enabled glob ignore patterns by default and removed the `unstable_glob_ignore` feature.
|
||||
* `globIgnorePaths` can be set on a project to a list of globs to ignore.
|
||||
* The Rojo plugin now completes as much as it can from a patch without disconnecting. Warnings are shown in the console.
|
||||
* Fixed 6.0.0-rc.1 regression causing instances that changed ClassName to instead... not change ClassName.
|
||||
|
||||
## [6.0.0 Release Candidate 1](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.0-rc.1) (March 29, 2020)
|
||||
This release jumped from 0.6.0 to 6.0.0. Rojo has been in use in production for many users for quite a long times, and so 6.0 is a more accurate reflection of Rojo's version than a pre-1.0 version.
|
||||
|
||||
* Added basic settings panel to plugin, with two settings:
|
||||
* "Open Scripts Externally": When enabled, opening a script in Studio will instead open it in your default text editor.
|
||||
* "Two-Way Sync": When enabled, Rojo will attempt to save changes to your place back to the filesystem. **Very early feature, very broken, beware!**
|
||||
* Added `--color` option to force-enable or force-disable color in Rojo's output.
|
||||
* Added support for turning `.json` files into `ModuleScript` instances ([#308](https://github.com/rojo-rbx/rojo/pull/308))
|
||||
* Added `rojo plugin install` and `rojo plugin uninstall` to allow Rojo to manage its Roblox Studio plugin. ([#304](https://github.com/rojo-rbx/rojo/pull/304))
|
||||
* Class names no longer need to be specified for Roblox services in Rojo projects. ([#210](https://github.com/rojo-rbx/rojo/pull/210))
|
||||
* The server half of **experimental** two-way sync is now enabled by default.
|
||||
* Increased default logging verbosity in commands like `rojo build`.
|
||||
* Rojo now requires a project file again, just like 0.5.4.
|
||||
|
||||
## [0.6.0 Alpha 3](https://github.com/rojo-rbx/rojo/releases/tag/v0.6.0-alpha.3) (March 13, 2020)
|
||||
* Added `--watch` argument to `rojo build`. ([#284](https://github.com/rojo-rbx/rojo/pull/284))
|
||||
* Added dark theme support to plugin. ([#241](https://github.com/rojo-rbx/rojo/issues/241))
|
||||
* Added a revamped `rojo init` command, which will now create more complete projects.
|
||||
* Added the `rojo doc` command, which opens Rojo's documentation in your browser.
|
||||
* Fixed many crashes from malformed projects and filesystem edge cases in `rojo serve`.
|
||||
* Simplified filesystem access code dramatically.
|
||||
* Improved error reporting and logging across the board.
|
||||
* Log messages have a less noisy prefix.
|
||||
* Any thread panicking now causes Rojo to abort instead of existing as a zombie.
|
||||
* Errors now have a list of causes, helping make many errors more clear.
|
||||
|
||||
## [0.6.0 Alpha 2](https://github.com/rojo-rbx/rojo/releases/tag/v0.6.0-alpha.2) (March 6, 2020)
|
||||
* Fixed `rojo upload` command always uploading models.
|
||||
@@ -98,7 +278,7 @@ This is a general maintenance release for the Rojo 0.5.x release series.
|
||||
|
||||
## [0.5.0 Alpha 9](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.9) (April 4, 2019)
|
||||
* Changed `rojo build` to use buffered I/O, which can make it up to 2x faster in some cases.
|
||||
* Building [*Road Not Taken*](https://github.com/rojo-rbx/roads) to an `rbxlx` file dropped from 150ms to 70ms on my machine
|
||||
* Building [*Road Not Taken*](https://github.com/LPGhatguy/roads) to an `rbxlx` file dropped from 150ms to 70ms on my machine
|
||||
* Fixed `LocalizationTable` instances being made from `csv` files incorrectly interpreting empty rows and columns. ([#149](https://github.com/rojo-rbx/rojo/pull/149))
|
||||
* Fixed CSV files with entries that parse as numbers causing Rojo to panic. ([#152](https://github.com/rojo-rbx/rojo/pull/152))
|
||||
* Improved error messages when malformed CSV files are found in a Rojo project.
|
||||
@@ -295,4 +475,4 @@ This is a general maintenance release for the Rojo 0.5.x release series.
|
||||
* More robust syncing with a new reconciler
|
||||
|
||||
## [0.1.0](https://github.com/rojo-rbx/rojo/releases/tag/v0.1.0) (November 29, 2017)
|
||||
* Initial release, functionally very similar to [rbxfs](https://github.com/rojo-rbx/rbxfs)
|
||||
* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs)
|
||||
@@ -1,5 +1,5 @@
|
||||
# Contributing to the Rojo Project
|
||||
Rojo is a big project and can always use more help! This guide covers all repositories underneath the [rojo-rbx organization on GitHub](https://github.com/rojo-rbx).
|
||||
Rojo is a big project and can always use more help!
|
||||
|
||||
Some of the repositories covered are:
|
||||
|
||||
@@ -15,8 +15,7 @@ You'll want these tools to work on Rojo:
|
||||
|
||||
* Latest stable Rust compiler
|
||||
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
||||
* Latest stable [Remodel](https://github.com/rojo-rbx/remodel)
|
||||
* Latest stable [run-in-roblox](https://github.com/rojo-rbx/run-in-roblox)
|
||||
* [Foreman](https://github.com/Roblox/foreman)
|
||||
|
||||
## Documentation
|
||||
Documentation impacts way more people than the individual lines of code we write.
|
||||
@@ -30,26 +29,31 @@ Sometimes there's something that Rojo doesn't do that it probably should.
|
||||
|
||||
Please file issues and we'll try to help figure out what the best way forward is.
|
||||
|
||||
## Local Development Gotchas
|
||||
|
||||
If your build fails with "Error: failed to open file `D:\code\rojo\plugin\modules\roact\src`" you need to update your Git submodules.
|
||||
Run the command and try building again: `git submodule update --init --recursive`.
|
||||
|
||||
## Pushing a Rojo Release
|
||||
The Rojo release process is pretty manual right now. If you need to do it, here's how:
|
||||
|
||||
1. Bump server version in [`server/Cargo.toml`](server/Cargo.toml)
|
||||
1. Bump server version in [`Cargo.toml`](Cargo.toml)
|
||||
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
|
||||
3. Run `cargo test` to update `Cargo.lock` and double-check tests
|
||||
3. Run `cargo test` to update `Cargo.lock` and run tests
|
||||
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
||||
5. Commit!
|
||||
* `git add . && git commit -m "Release vX.Y.Z"`
|
||||
6. Tag the commit with the version from `Cargo.toml` prepended with a v, like `v0.4.13`
|
||||
7. Build Windows release build of CLI
|
||||
* `cargo build --release`
|
||||
6. Tag the commit
|
||||
* `git tag vX.Y.Z`
|
||||
7. Publish the CLI
|
||||
* `cargo publish`
|
||||
8. Build and upload the plugin
|
||||
* `rojo build plugin -o Rojo.rbxm`
|
||||
* Upload `Rojo.rbxm` to Roblox.com, keep it for later
|
||||
8. Publish the Plugin
|
||||
* `cargo run -- upload plugin --asset_id 6415005344`
|
||||
* `cargo run -- build plugin --output Rojo.rbxm`
|
||||
9. Push commits and tags
|
||||
* `git push && git push --tags`
|
||||
10. Copy GitHub release content from previous release
|
||||
* Update the leading text with a summary about the release
|
||||
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
||||
* Write a small summary of each major feature
|
||||
* Write a small summary of each major feature
|
||||
* Attach release artifacts from GitHub Actions for each platform
|
||||
|
||||
3064
Cargo.lock
generated
124
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rojo"
|
||||
version = "0.6.0-alpha.2"
|
||||
version = "7.0.0"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
description = "Enables professional-grade development tools for Roblox developers"
|
||||
license = "MPL-2.0"
|
||||
@@ -9,35 +9,26 @@ documentation = "https://rojo.space/docs"
|
||||
repository = "https://github.com/rojo-rbx/rojo"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
exclude = [
|
||||
"/plugin/**",
|
||||
"/test-projects/**",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Turn on support for specifying glob ignore path rules in the project format.
|
||||
unstable_glob_ignore_paths = []
|
||||
|
||||
# Turn on the server half of Rojo's unstable two-way sync feature.
|
||||
unstable_two_way_sync = []
|
||||
|
||||
# Enable this feature to live-reload assets from the web UI.
|
||||
dev_live_assets = []
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"rojo-test",
|
||||
"rojo-insta-ext",
|
||||
"clibrojo",
|
||||
"memofs",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
".",
|
||||
"rojo-test",
|
||||
"rojo-insta-ext",
|
||||
"memofs",
|
||||
]
|
||||
@@ -46,55 +37,76 @@ default-members = [
|
||||
name = "librojo"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rojo"
|
||||
path = "src/bin.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "build"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "0.4.0"
|
||||
csv = "1.1.1"
|
||||
env_logger = "0.7.1"
|
||||
futures = "0.1.29"
|
||||
globset = "0.4.4"
|
||||
humantime = "1.3.0"
|
||||
hyper = "0.12.35"
|
||||
jod-thread = "0.1.0"
|
||||
memofs = { version = "0.2.0", path = "memofs" }
|
||||
|
||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
||||
# rbx_dom_weak = { path = "../rbx-dom/rbx_dom_weak" }
|
||||
# rbx_reflection = { path = "../rbx-dom/rbx_reflection" }
|
||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
||||
|
||||
rbx_binary = "0.6.4"
|
||||
rbx_dom_weak = "2.3.0"
|
||||
rbx_reflection = "4.2.0"
|
||||
rbx_reflection_database = "0.2.2"
|
||||
rbx_xml = "0.12.3"
|
||||
|
||||
anyhow = "1.0.44"
|
||||
backtrace = "0.3.61"
|
||||
bincode = "1.3.3"
|
||||
crossbeam-channel = "0.5.1"
|
||||
csv = "1.1.6"
|
||||
env_logger = "0.9.0"
|
||||
fs-err = "2.6.0"
|
||||
futures = "0.3.17"
|
||||
globset = "0.4.8"
|
||||
humantime = "2.1.0"
|
||||
hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] }
|
||||
jod-thread = "0.1.2"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.8"
|
||||
maplit = "1.0.1"
|
||||
notify = "4.0.14"
|
||||
rbx_binary = "0.5.0"
|
||||
rbx_dom_weak = "1.10.1"
|
||||
rbx_reflection = "3.3.408"
|
||||
rbx_xml = "0.11.3"
|
||||
regex = "1.3.1"
|
||||
reqwest = "0.9.20"
|
||||
log = "0.4.14"
|
||||
maplit = "1.0.2"
|
||||
notify = "4.0.17"
|
||||
opener = "0.5.0"
|
||||
regex = "1.5.4"
|
||||
reqwest = "0.9.24"
|
||||
ritz = "0.1.0"
|
||||
rlua = "0.17.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
snafu = "0.6.0"
|
||||
structopt = "0.3.5"
|
||||
termcolor = "1.0.5"
|
||||
tokio = "0.1.22"
|
||||
uuid = { version = "0.8.1", features = ["v4", "serde"] }
|
||||
memofs = { path = "memofs" }
|
||||
rlua = "0.17.1"
|
||||
roblox_install = "1.0.0"
|
||||
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.68"
|
||||
structopt = "0.3.23"
|
||||
termcolor = "1.1.2"
|
||||
thiserror = "1.0.30"
|
||||
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
|
||||
uuid = { version = "0.8.2", features = ["v4", "serde"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.6.2"
|
||||
winreg = "0.9.0"
|
||||
|
||||
[build-dependencies]
|
||||
memofs = { version = "0.2.0", path = "memofs" }
|
||||
|
||||
embed-resource = "1.6.4"
|
||||
anyhow = "1.0.44"
|
||||
bincode = "1.3.3"
|
||||
fs-err = "2.6.0"
|
||||
maplit = "1.0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
rojo-insta-ext = { path = "rojo-insta-ext" }
|
||||
|
||||
criterion = "0.3"
|
||||
insta = { version = "0.13.1", features = ["redactions"] }
|
||||
lazy_static = "1.2"
|
||||
paste = "0.1"
|
||||
pretty_assertions = "0.6.1"
|
||||
serde_yaml = "0.8.9"
|
||||
tempfile = "3.0"
|
||||
walkdir = "2.1"
|
||||
criterion = "0.3.5"
|
||||
insta = { version = "1.8.0", features = ["redactions"] }
|
||||
lazy_static = "1.4.0"
|
||||
paste = "1.0.5"
|
||||
pretty_assertions = "0.7.2"
|
||||
serde_yaml = "0.8.21"
|
||||
tempfile = "3.2.0"
|
||||
walkdir = "2.3.2"
|
||||
|
||||
23
README.md
@@ -1,21 +1,13 @@
|
||||
<div align="center">
|
||||
<a href="https://rojo.space">
|
||||
<img src="assets/logo-512.png" alt="Rojo" height="217" />
|
||||
</a>
|
||||
<a href="https://rojo.space"><img src="assets/logo-512.png" alt="Rojo" height="217" /></a>
|
||||
</div>
|
||||
|
||||
<div> </div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/rojo-rbx/rojo/actions">
|
||||
<img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" />
|
||||
</a>
|
||||
<a href="https://crates.io/crates/rojo">
|
||||
<img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" />
|
||||
</a>
|
||||
<a href="https://rojo.space/docs/0.5.x">
|
||||
<img src="https://img.shields.io/badge/docs-0.5.x-brightgreen.svg" alt="Rojo 0.5.x Documentation" />
|
||||
</a>
|
||||
<a href="https://github.com/rojo-rbx/rojo/actions"><img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" /></a>
|
||||
<a href="https://crates.io/crates/rojo"><img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" /></a>
|
||||
<a href="https://rojo.space/docs"><img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" /></a>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
@@ -34,11 +26,10 @@ Rojo enables:
|
||||
* Streaming `rbxmx` and `rbxm` models into your game in real time
|
||||
* Packaging and deploying your project to Roblox.com from the command line
|
||||
|
||||
Soon, Rojo will be able to:
|
||||
In the future, Rojo will be able to:
|
||||
|
||||
* Automatically convert your existing game to work with Rojo
|
||||
* Sync instances from Roblox Studio to the filesystem
|
||||
* Automatically manage your assets on Roblox.com, like images and sounds
|
||||
* Automatically convert your existing game to work with Rojo
|
||||
* Import custom instances like MoonScript code
|
||||
|
||||
## [Documentation](https://rojo.space/docs)
|
||||
@@ -49,7 +40,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
|
||||
|
||||
Pull requests are welcome!
|
||||
|
||||
Rojo supports Rust 1.40.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||
Rojo supports Rust 1.46.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||
|
||||
## License
|
||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||
11
assets/default-model-project/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# {project_name}
|
||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||
|
||||
## Getting Started
|
||||
To build this library or plugin, use:
|
||||
|
||||
```bash
|
||||
rojo build -o "{project_name}.rbxmx"
|
||||
```
|
||||
|
||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
||||
6
assets/default-model-project/default.project.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "{project_name}",
|
||||
"tree": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
3
assets/default-model-project/gitignore.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# Roblox Studio lock files
|
||||
/*.rbxlx.lock
|
||||
/*.rbxl.lock
|
||||
5
assets/default-model-project/src-init.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
return {
|
||||
hello = function()
|
||||
print("Hello world, from {project_name}!")
|
||||
end,
|
||||
}
|
||||
17
assets/default-place-project/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# {project_name}
|
||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||
|
||||
## Getting Started
|
||||
To build the place from scratch, use:
|
||||
|
||||
```bash
|
||||
rojo build -o "{project_name}.rbxlx"
|
||||
```
|
||||
|
||||
Next, open `{project_name}.rbxlx` in Roblox Studio and start the Rojo server:
|
||||
|
||||
```bash
|
||||
rojo serve
|
||||
```
|
||||
|
||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
||||
@@ -1,41 +1,29 @@
|
||||
{
|
||||
"name": "[placeholder]",
|
||||
"name": "{project_name}",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
"HttpService": {
|
||||
"$className": "HttpService",
|
||||
"$properties": {
|
||||
"HttpEnabled": true
|
||||
}
|
||||
},
|
||||
"Lighting": {
|
||||
"$className": "Lighting",
|
||||
"$properties": {
|
||||
"Ambient": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"Brightness": 2,
|
||||
"GlobalShadows": true,
|
||||
"Outlines": false,
|
||||
"Technology": "Voxel"
|
||||
}
|
||||
},
|
||||
|
||||
"ReplicatedStorage": {
|
||||
"$className": "ReplicatedStorage",
|
||||
"Source": {
|
||||
"$path": "src"
|
||||
"Common": {
|
||||
"$path": "src/shared"
|
||||
}
|
||||
},
|
||||
"SoundService": {
|
||||
"$className": "SoundService",
|
||||
"$properties": {
|
||||
"RespectFilteringEnabled": true
|
||||
|
||||
"ServerScriptService": {
|
||||
"Server": {
|
||||
"$path": "src/server"
|
||||
}
|
||||
},
|
||||
|
||||
"StarterPlayer": {
|
||||
"StarterPlayerScripts": {
|
||||
"Client": {
|
||||
"$path": "src/client"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"Workspace": {
|
||||
"$className": "Workspace",
|
||||
"$properties": {
|
||||
"FilteringEnabled": true
|
||||
},
|
||||
@@ -61,6 +49,24 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Lighting": {
|
||||
"$properties": {
|
||||
"Ambient": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"Brightness": 2,
|
||||
"GlobalShadows": true,
|
||||
"Outlines": false,
|
||||
"Technology": "Voxel"
|
||||
}
|
||||
},
|
||||
"SoundService": {
|
||||
"$properties": {
|
||||
"RespectFilteringEnabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
assets/default-place-project/gitignore.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# Project place file
|
||||
/{project_name}.rbxlx
|
||||
|
||||
# Roblox Studio lock files
|
||||
/*.rbxlx.lock
|
||||
/*.rbxl.lock
|
||||
BIN
assets/images/back.png
Normal file
|
After Width: | Height: | Size: 229 B |
5
assets/images/back.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M20,11L20,13L8,13L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11L20,11Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 584 B |
BIN
assets/images/checkbox-off.png
Normal file
|
After Width: | Height: | Size: 269 B |
15
assets/images/checkbox-off.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g transform="matrix(1,0,0,1,-54,-1)">
|
||||
<g id="Artboard3" transform="matrix(1.77778,0,0,1.45455,-42,-0.454545)">
|
||||
<rect x="54" y="1" width="9" height="11" style="fill:none;"/>
|
||||
<g transform="matrix(3.375,0,0,4.125,-3654,-2753.12)">
|
||||
<path d="M1099,670L1101,668" style="fill:none;stroke:white;stroke-width:0.5px;"/>
|
||||
<g transform="matrix(-1,0,0,1,2200,0)">
|
||||
<path d="M1099,670L1101,668" style="fill:none;stroke:white;stroke-width:0.5px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/checkbox-on.png
Normal file
|
After Width: | Height: | Size: 249 B |
12
assets/images/checkbox-on.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g transform="matrix(1,0,0,1,-32,0)">
|
||||
<g id="Artboard2" transform="matrix(0.8,0,0,0.941176,6.4,0)">
|
||||
<rect x="32" y="0" width="20" height="17" style="fill:none;"/>
|
||||
<g transform="matrix(5,0,0,4.25,-5470.5,-2371.5)">
|
||||
<path d="M1101,560L1102,561L1104,559" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 870 B |
BIN
assets/images/circle-128.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/images/circle-16.png
Normal file
|
After Width: | Height: | Size: 196 B |
BIN
assets/images/circle-32.png
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
assets/images/circle-500.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
assets/images/circle-64.png
Normal file
|
After Width: | Height: | Size: 613 B |
BIN
assets/images/close.png
Normal file
|
After Width: | Height: | Size: 295 B |
15
assets/images/close.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g id="Artboard1" transform="matrix(0.0666667,0,0,0.097561,-31,-18.6341)">
|
||||
<rect x="465" y="191" width="360" height="246" style="fill:none;"/>
|
||||
<g transform="matrix(134.328,0,0,102.5,-74228.5,-15214.7)">
|
||||
<g transform="matrix(1.11667,0,0,1,-57.3333,0)">
|
||||
<path d="M551,152L550,151" style="fill:none;stroke:white;stroke-width:0.3px;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.11667,0,0,1,-57.3333,0)">
|
||||
<path d="M550,152L551,151" style="fill:none;stroke:white;stroke-width:0.3px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/header-logo.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
21
assets/images/header-logo.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 60 27" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Artboard1" transform="matrix(0.952381,0,0,0.710526,-228.571,-156.316)">
|
||||
<rect x="240" y="220" width="63" height="38" style="fill:none;"/>
|
||||
<g transform="matrix(0.0789166,0,0,0.105779,211.848,170.749)">
|
||||
<g transform="matrix(340.635,0,0,340.635,376,753)">
|
||||
<path d="M0.302,-0.836L0.306,-0.836L0.302,-0.829L0.302,-0.814C0.333,-0.82 0.349,-0.828 0.349,-0.836L0.371,-0.833C0.408,-0.835 0.441,-0.836 0.472,-0.836L0.476,-0.836C0.524,-0.836 0.58,-0.81 0.646,-0.757C0.66,-0.734 0.668,-0.714 0.672,-0.695L0.672,-0.659C0.664,-0.609 0.65,-0.571 0.632,-0.546C0.621,-0.537 0.597,-0.507 0.56,-0.456L0.556,-0.456L0.556,-0.463L0.566,-0.478L0.56,-0.478C0.474,-0.405 0.378,-0.349 0.27,-0.311C0.243,-0.304 0.216,-0.3 0.19,-0.3C0.224,-0.26 0.287,-0.207 0.378,-0.141C0.387,-0.136 0.445,-0.099 0.552,-0.028C0.641,0.027 0.747,0.087 0.871,0.153L0.871,0.157L0.856,0.157C0.832,0.148 0.82,0.141 0.82,0.138L0.813,0.142L0.806,0.142C0.803,0.142 0.802,0.141 0.802,0.138C0.793,0.14 0.786,0.149 0.78,0.164C0.795,0.168 0.802,0.173 0.802,0.178C0.796,0.183 0.792,0.186 0.788,0.186L0.77,0.182L0.77,0.186C0.77,0.19 0.773,0.193 0.78,0.193L0.78,0.204L0.777,0.204C0.772,0.204 0.717,0.172 0.614,0.109C0.541,0.072 0.451,0.015 0.346,-0.061C0.311,-0.077 0.247,-0.124 0.153,-0.202L0.143,-0.202C0.129,-0.181 0.111,-0.129 0.088,-0.046C0.083,-0.027 0.059,0.001 0.016,0.037L0.008,0.037L0.001,0.026L0.001,0.022C0.001,0.017 0.005,0.006 0.012,-0.01L0.012,-0.014L0.008,-0.014C0.008,-0.007 -0.007,0.004 -0.035,0.019L-0.039,0.019L-0.039,0.012C0.046,-0.24 0.105,-0.404 0.139,-0.481C0.197,-0.614 0.226,-0.69 0.226,-0.709C0.213,-0.709 0.184,-0.693 0.139,-0.659L0.132,-0.659L0.135,-0.666L0.135,-0.673C0.122,-0.673 0.092,-0.652 0.045,-0.608L0.041,-0.608L0.041,-0.612L0.088,-0.666L0.096,-0.677L0.096,-0.681L0.088,-0.681L0.045,-0.645C0.04,-0.645 0.038,-0.647 0.038,-0.651C0.083,-0.701 0.138,-0.744 0.204,-0.778C0.265,-0.794 0.295,-0.812 0.295,-0.833L0.302,-0.836ZM0.632,-0.735L0.632,-0.731C0.632,-0.727 0.635,-0.724 0.639,-0.724L0.639,-0.728C0.639,-0.732 0.637,-0.735 0.632,-0.735ZM0.208,-0.387L0.211,-0.387C0.255,-0.396 0.277,-0.403 0.277,-0.409C0.274,-0.414 0.273,-0.417 0.273,-0.42C0.365,-0.451 0.427,-0.482 0.458,-0.514C0.461,-0.514 0.48,-0.534 0.516,-0.576L0.52,-0.576L0.52,-0.572C0.515,-0.564 0.512,-0.558 0.512,-0.554L0.516,-0.554C0.547,-0.578 0.563,-0.604 0.563,-0.633L0.563,-0.655C0.556,-0.655 0.552,-0.658 0.552,-0.663C0.564,-0.665 0.57,-0.671 0.57,-0.681C0.57,-0.693 0.546,-0.705 0.498,-0.717C0.459,-0.727 0.417,-0.731 0.371,-0.731C0.348,-0.731 0.325,-0.689 0.302,-0.604C0.292,-0.594 0.261,-0.522 0.208,-0.387ZM0.251,-0.695L0.251,-0.691C0.255,-0.691 0.258,-0.695 0.262,-0.702L0.262,-0.706C0.259,-0.706 0.255,-0.702 0.251,-0.695ZM0.255,-0.626L0.259,-0.626C0.266,-0.629 0.27,-0.636 0.27,-0.648C0.266,-0.648 0.261,-0.641 0.255,-0.626ZM0.596,-0.612L0.596,-0.604C0.599,-0.604 0.603,-0.608 0.606,-0.615L0.606,-0.619L0.603,-0.619C0.598,-0.618 0.596,-0.616 0.596,-0.612ZM0.204,-0.369L0.204,-0.365L0.211,-0.365C0.216,-0.368 0.22,-0.369 0.222,-0.369L0.222,-0.365C0.281,-0.376 0.316,-0.388 0.328,-0.401L0.324,-0.401C0.271,-0.389 0.231,-0.378 0.204,-0.369ZM0.19,-0.333L0.193,-0.333L0.259,-0.347L0.255,-0.354C0.212,-0.349 0.19,-0.341 0.19,-0.333ZM0.334,-0.108L0.334,-0.101C0.372,-0.072 0.395,-0.057 0.403,-0.057C0.394,-0.064 0.389,-0.07 0.389,-0.075L0.334,-0.108ZM0.44,-0.028C0.442,-0.019 0.449,-0.014 0.461,-0.014L0.465,-0.014L0.465,-0.018C0.459,-0.018 0.452,-0.021 0.443,-0.028L0.44,-0.028ZM0.675,0.102C0.694,0.119 0.723,0.136 0.762,0.153L0.766,0.146C0.736,0.128 0.708,0.113 0.683,0.102L0.675,0.102ZM0.875,0.16L0.886,0.16C0.89,0.161 0.893,0.163 0.893,0.167C0.888,0.167 0.886,0.171 0.886,0.178L0.878,0.178L0.882,0.171L0.882,0.167L0.875,0.167L0.875,0.16Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(340.635,0,0,340.635,588.398,753)">
|
||||
<path d="M0.631,-0.705C0.646,-0.705 0.661,-0.696 0.673,-0.678L0.646,-0.678C0.62,-0.678 0.587,-0.67 0.546,-0.653L0.546,-0.65L0.549,-0.65C0.607,-0.654 0.649,-0.656 0.677,-0.656L0.683,-0.656L0.683,-0.65L0.67,-0.65C0.668,-0.65 0.667,-0.651 0.667,-0.653L0.503,-0.629C0.503,-0.627 0.493,-0.624 0.473,-0.62L0.473,-0.614L0.519,-0.623L0.521,-0.623L0.521,-0.617C0.386,-0.581 0.318,-0.555 0.318,-0.538C0.252,-0.483 0.204,-0.431 0.175,-0.383C0.131,-0.304 0.108,-0.243 0.108,-0.201C0.108,-0.157 0.131,-0.125 0.175,-0.104C0.183,-0.102 0.19,-0.101 0.196,-0.101C0.286,-0.136 0.353,-0.196 0.397,-0.28C0.409,-0.318 0.419,-0.363 0.427,-0.417L0.434,-0.417L0.427,-0.359C0.433,-0.359 0.439,-0.374 0.443,-0.404C0.441,-0.412 0.439,-0.42 0.439,-0.429L0.439,-0.438L0.446,-0.438C0.448,-0.407 0.449,-0.386 0.449,-0.374C0.449,-0.358 0.444,-0.331 0.434,-0.292C0.445,-0.302 0.458,-0.335 0.473,-0.392C0.48,-0.447 0.486,-0.474 0.491,-0.474C0.491,-0.472 0.492,-0.471 0.494,-0.471C0.492,-0.469 0.49,-0.457 0.488,-0.435L0.488,-0.432L0.491,-0.432C0.499,-0.454 0.504,-0.48 0.506,-0.508C0.502,-0.516 0.5,-0.522 0.5,-0.526L0.506,-0.529C0.512,-0.529 0.518,-0.513 0.525,-0.48C0.525,-0.4 0.491,-0.297 0.424,-0.17C0.372,-0.093 0.305,-0.042 0.224,-0.019C0.188,-0.006 0.154,-0 0.121,-0C0.04,-0.022 -0.001,-0.083 -0.001,-0.183C-0.001,-0.296 0.045,-0.398 0.136,-0.489C0.165,-0.522 0.227,-0.568 0.321,-0.629C0.425,-0.68 0.529,-0.705 0.631,-0.705ZM0.482,-0.656L0.482,-0.653C0.499,-0.653 0.508,-0.652 0.509,-0.65C0.526,-0.659 0.536,-0.663 0.54,-0.663L0.54,-0.668L0.482,-0.656ZM0.099,-0.417C0.144,-0.458 0.179,-0.494 0.206,-0.523C0.23,-0.538 0.242,-0.55 0.242,-0.559C0.201,-0.535 0.156,-0.493 0.108,-0.432C0.102,-0.427 0.099,-0.422 0.099,-0.417ZM0.105,-0.261C0.108,-0.261 0.118,-0.284 0.136,-0.328C0.158,-0.378 0.203,-0.439 0.272,-0.511L0.272,-0.514C0.204,-0.463 0.167,-0.422 0.16,-0.392C0.142,-0.371 0.124,-0.328 0.105,-0.261ZM0.069,-0.334L0.069,-0.332L0.072,-0.332C0.116,-0.393 0.139,-0.429 0.139,-0.438C0.11,-0.413 0.086,-0.378 0.069,-0.334ZM0.467,-0.31L0.467,-0.307L0.47,-0.307C0.492,-0.371 0.503,-0.413 0.503,-0.432C0.49,-0.409 0.478,-0.369 0.467,-0.31ZM0.087,-0.404L0.087,-0.401C0.091,-0.401 0.093,-0.403 0.093,-0.407L0.093,-0.41C0.089,-0.41 0.087,-0.408 0.087,-0.404ZM0.063,-0.368C0.032,-0.297 0.017,-0.245 0.017,-0.213C0.021,-0.148 0.028,-0.107 0.039,-0.088L0.042,-0.088L0.042,-0.094C0.035,-0.107 0.032,-0.129 0.032,-0.158C0.032,-0.191 0.035,-0.227 0.042,-0.268L0.039,-0.273C0.045,-0.3 0.054,-0.331 0.066,-0.365L0.066,-0.368L0.063,-0.368ZM0.096,-0.24L0.096,-0.237C0.1,-0.237 0.103,-0.239 0.103,-0.243L0.103,-0.246C0.098,-0.245 0.096,-0.243 0.096,-0.24ZM0.397,-0.222L0.397,-0.219L0.4,-0.219C0.408,-0.232 0.412,-0.241 0.412,-0.243C0.407,-0.242 0.402,-0.235 0.397,-0.222ZM0.066,-0.24C0.06,-0.222 0.055,-0.202 0.051,-0.179C0.055,-0.138 0.061,-0.108 0.069,-0.088L0.072,-0.088L0.072,-0.094C0.066,-0.121 0.063,-0.139 0.063,-0.146L0.066,-0.152C0.064,-0.155 0.063,-0.16 0.063,-0.164C0.063,-0.169 0.064,-0.173 0.066,-0.176C0.064,-0.18 0.063,-0.183 0.063,-0.186C0.067,-0.218 0.069,-0.235 0.069,-0.237L0.069,-0.24L0.066,-0.24ZM0.294,-0.125L0.294,-0.122C0.3,-0.122 0.322,-0.143 0.357,-0.186L0.357,-0.188L0.354,-0.188C0.314,-0.151 0.294,-0.129 0.294,-0.125ZM0.379,-0.149C0.379,-0.146 0.36,-0.126 0.321,-0.088L0.324,-0.088C0.351,-0.112 0.37,-0.132 0.382,-0.146L0.385,-0.146L0.385,-0.149L0.379,-0.149ZM0.09,-0.104L0.09,-0.101C0.094,-0.08 0.104,-0.07 0.121,-0.07L0.13,-0.07L0.13,-0.073L0.09,-0.104ZM0.151,-0.055L0.188,-0.055L0.203,-0.058L0.203,-0.061L0.157,-0.061C0.153,-0.06 0.151,-0.058 0.151,-0.055ZM0.096,-0.058L0.096,-0.055C0.104,-0.045 0.113,-0.04 0.124,-0.04L0.13,-0.04L0.13,-0.042C0.127,-0.042 0.116,-0.048 0.096,-0.058Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(340.635,0,0,340.635,758.882,753)">
|
||||
<path d="M0.208,-0.7L0.223,-0.7C0.227,-0.7 0.229,-0.697 0.229,-0.691C0.294,-0.687 0.328,-0.684 0.331,-0.682C0.333,-0.682 0.333,-0.683 0.333,-0.685C0.364,-0.683 0.387,-0.682 0.401,-0.682L0.462,-0.682C0.466,-0.682 0.468,-0.684 0.468,-0.688L0.45,-0.688L0.45,-0.694L0.49,-0.694L0.49,-0.688L0.475,-0.688L0.475,-0.682L0.557,-0.682C0.564,-0.682 0.573,-0.681 0.584,-0.679C0.603,-0.681 0.612,-0.685 0.612,-0.691L0.658,-0.691C0.678,-0.691 0.688,-0.692 0.689,-0.694C0.706,-0.692 0.72,-0.691 0.731,-0.691L0.903,-0.691L0.921,-0.688L0.928,-0.691C1.035,-0.686 1.096,-0.681 1.111,-0.676L1.13,-0.682L1.13,-0.676L1.068,-0.667L1.068,-0.664C1.075,-0.664 1.078,-0.661 1.078,-0.657C1.078,-0.654 1.075,-0.651 1.068,-0.648C1.064,-0.65 1.061,-0.651 1.059,-0.651C1.059,-0.647 1.055,-0.645 1.047,-0.645L1.047,-0.642C1.048,-0.638 1.05,-0.636 1.053,-0.636L1.059,-0.636C1.072,-0.636 1.087,-0.635 1.105,-0.633C1.113,-0.635 1.12,-0.636 1.126,-0.636C1.139,-0.625 1.145,-0.619 1.145,-0.618C1.138,-0.613 1.132,-0.611 1.126,-0.611L1.099,-0.611C1.088,-0.611 1.079,-0.608 1.071,-0.602L1.029,-0.602C1.024,-0.602 1.02,-0.603 1.017,-0.605L1.01,-0.602C0.993,-0.604 0.98,-0.605 0.971,-0.605C0.962,-0.605 0.958,-0.604 0.958,-0.602L0.928,-0.605L0.695,-0.605C0.69,-0.605 0.686,-0.604 0.683,-0.602C0.679,-0.604 0.675,-0.605 0.67,-0.605L0.563,-0.605C0.555,-0.605 0.547,-0.604 0.539,-0.602C0.531,-0.604 0.523,-0.605 0.514,-0.605L0.453,-0.605C0.429,-0.578 0.401,-0.525 0.37,-0.446C0.37,-0.439 0.334,-0.358 0.263,-0.201L0.177,-0.021C0.185,-0.001 0.189,0.011 0.189,0.013C0.186,0.013 0.177,0.001 0.162,-0.023L0.156,-0.023L0.153,-0.008C0.159,-0.001 0.162,0.006 0.162,0.013L0.162,0.017L0.15,0.007L0.128,0.007C0.128,-0.005 0.108,-0.045 0.067,-0.112C0.012,-0.235 -0.028,-0.332 -0.052,-0.403L-0.052,-0.412C-0.048,-0.412 -0.046,-0.409 -0.046,-0.403L-0.043,-0.403C-0.039,-0.403 -0.037,-0.405 -0.037,-0.409L-0.046,-0.443L-0.046,-0.446L-0.04,-0.446L0.034,-0.293L0.037,-0.293L0.037,-0.296C0.008,-0.37 -0.006,-0.41 -0.006,-0.416L-0.006,-0.421C0.009,-0.398 0.037,-0.343 0.076,-0.256C0.118,-0.177 0.142,-0.135 0.147,-0.13C0.212,-0.285 0.285,-0.442 0.364,-0.602C0.36,-0.602 0.358,-0.604 0.358,-0.608L0.352,-0.605L0.333,-0.605C0.332,-0.605 0.331,-0.606 0.331,-0.608C0.327,-0.606 0.324,-0.605 0.321,-0.605L0.315,-0.608C0.313,-0.608 0.312,-0.607 0.312,-0.605L0.291,-0.608L0.263,-0.608C0.263,-0.615 0.261,-0.618 0.257,-0.618L0.254,-0.618C0.247,-0.618 0.215,-0.621 0.159,-0.626L0.159,-0.63L0.162,-0.636L0.156,-0.636L0.156,-0.639C0.156,-0.641 0.163,-0.646 0.177,-0.654L0.184,-0.654L0.189,-0.651C0.189,-0.653 0.191,-0.659 0.193,-0.669C0.188,-0.671 0.178,-0.674 0.162,-0.676L0.162,-0.682L0.184,-0.682C0.191,-0.682 0.199,-0.688 0.208,-0.7ZM0.291,-0.697L0.352,-0.697C0.356,-0.697 0.358,-0.694 0.358,-0.691L0.291,-0.691L0.291,-0.697ZM0.41,-0.694L0.419,-0.694L0.419,-0.688L0.41,-0.688L0.41,-0.694ZM0.505,-0.694L0.533,-0.694L0.533,-0.688L0.505,-0.688L0.505,-0.694ZM0.603,-0.694L0.603,-0.691C0.603,-0.687 0.601,-0.685 0.597,-0.685L0.563,-0.685L0.563,-0.691L0.588,-0.691L0.603,-0.694ZM0.202,-0.667L0.202,-0.664L0.239,-0.664L0.239,-0.667L0.202,-0.667ZM0.269,-0.339L0.266,-0.327L0.266,-0.324L0.269,-0.324L0.272,-0.336L0.272,-0.339L0.269,-0.339ZM0.257,-0.308C0.237,-0.266 0.211,-0.205 0.177,-0.125C0.195,-0.147 0.223,-0.207 0.26,-0.305L0.26,-0.308L0.257,-0.308Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(340.635,0,0,340.635,891.444,753)">
|
||||
<path d="M0.631,-0.705C0.646,-0.705 0.661,-0.696 0.673,-0.678L0.646,-0.678C0.62,-0.678 0.587,-0.67 0.546,-0.653L0.546,-0.65L0.549,-0.65C0.607,-0.654 0.649,-0.656 0.677,-0.656L0.683,-0.656L0.683,-0.65L0.67,-0.65C0.668,-0.65 0.667,-0.651 0.667,-0.653L0.503,-0.629C0.503,-0.627 0.493,-0.624 0.473,-0.62L0.473,-0.614L0.519,-0.623L0.521,-0.623L0.521,-0.617C0.386,-0.581 0.318,-0.555 0.318,-0.538C0.252,-0.483 0.204,-0.431 0.175,-0.383C0.131,-0.304 0.108,-0.243 0.108,-0.201C0.108,-0.157 0.131,-0.125 0.175,-0.104C0.183,-0.102 0.19,-0.101 0.196,-0.101C0.286,-0.136 0.353,-0.196 0.397,-0.28C0.409,-0.318 0.419,-0.363 0.427,-0.417L0.434,-0.417L0.427,-0.359C0.433,-0.359 0.439,-0.374 0.443,-0.404C0.441,-0.412 0.439,-0.42 0.439,-0.429L0.439,-0.438L0.446,-0.438C0.448,-0.407 0.449,-0.386 0.449,-0.374C0.449,-0.358 0.444,-0.331 0.434,-0.292C0.445,-0.302 0.458,-0.335 0.473,-0.392C0.48,-0.447 0.486,-0.474 0.491,-0.474C0.491,-0.472 0.492,-0.471 0.494,-0.471C0.492,-0.469 0.49,-0.457 0.488,-0.435L0.488,-0.432L0.491,-0.432C0.499,-0.454 0.504,-0.48 0.506,-0.508C0.502,-0.516 0.5,-0.522 0.5,-0.526L0.506,-0.529C0.512,-0.529 0.518,-0.513 0.525,-0.48C0.525,-0.4 0.491,-0.297 0.424,-0.17C0.372,-0.093 0.305,-0.042 0.224,-0.019C0.188,-0.006 0.154,-0 0.121,-0C0.04,-0.022 -0.001,-0.083 -0.001,-0.183C-0.001,-0.296 0.045,-0.398 0.136,-0.489C0.165,-0.522 0.227,-0.568 0.321,-0.629C0.425,-0.68 0.529,-0.705 0.631,-0.705ZM0.482,-0.656L0.482,-0.653C0.499,-0.653 0.508,-0.652 0.509,-0.65C0.526,-0.659 0.536,-0.663 0.54,-0.663L0.54,-0.668L0.482,-0.656ZM0.099,-0.417C0.144,-0.458 0.179,-0.494 0.206,-0.523C0.23,-0.538 0.242,-0.55 0.242,-0.559C0.201,-0.535 0.156,-0.493 0.108,-0.432C0.102,-0.427 0.099,-0.422 0.099,-0.417ZM0.105,-0.261C0.108,-0.261 0.118,-0.284 0.136,-0.328C0.158,-0.378 0.203,-0.439 0.272,-0.511L0.272,-0.514C0.204,-0.463 0.167,-0.422 0.16,-0.392C0.142,-0.371 0.124,-0.328 0.105,-0.261ZM0.069,-0.334L0.069,-0.332L0.072,-0.332C0.116,-0.393 0.139,-0.429 0.139,-0.438C0.11,-0.413 0.086,-0.378 0.069,-0.334ZM0.467,-0.31L0.467,-0.307L0.47,-0.307C0.492,-0.371 0.503,-0.413 0.503,-0.432C0.49,-0.409 0.478,-0.369 0.467,-0.31ZM0.087,-0.404L0.087,-0.401C0.091,-0.401 0.093,-0.403 0.093,-0.407L0.093,-0.41C0.089,-0.41 0.087,-0.408 0.087,-0.404ZM0.063,-0.368C0.032,-0.297 0.017,-0.245 0.017,-0.213C0.021,-0.148 0.028,-0.107 0.039,-0.088L0.042,-0.088L0.042,-0.094C0.035,-0.107 0.032,-0.129 0.032,-0.158C0.032,-0.191 0.035,-0.227 0.042,-0.268L0.039,-0.273C0.045,-0.3 0.054,-0.331 0.066,-0.365L0.066,-0.368L0.063,-0.368ZM0.096,-0.24L0.096,-0.237C0.1,-0.237 0.103,-0.239 0.103,-0.243L0.103,-0.246C0.098,-0.245 0.096,-0.243 0.096,-0.24ZM0.397,-0.222L0.397,-0.219L0.4,-0.219C0.408,-0.232 0.412,-0.241 0.412,-0.243C0.407,-0.242 0.402,-0.235 0.397,-0.222ZM0.066,-0.24C0.06,-0.222 0.055,-0.202 0.051,-0.179C0.055,-0.138 0.061,-0.108 0.069,-0.088L0.072,-0.088L0.072,-0.094C0.066,-0.121 0.063,-0.139 0.063,-0.146L0.066,-0.152C0.064,-0.155 0.063,-0.16 0.063,-0.164C0.063,-0.169 0.064,-0.173 0.066,-0.176C0.064,-0.18 0.063,-0.183 0.063,-0.186C0.067,-0.218 0.069,-0.235 0.069,-0.237L0.069,-0.24L0.066,-0.24ZM0.294,-0.125L0.294,-0.122C0.3,-0.122 0.322,-0.143 0.357,-0.186L0.357,-0.188L0.354,-0.188C0.314,-0.151 0.294,-0.129 0.294,-0.125ZM0.379,-0.149C0.379,-0.146 0.36,-0.126 0.321,-0.088L0.324,-0.088C0.351,-0.112 0.37,-0.132 0.382,-0.146L0.385,-0.146L0.385,-0.149L0.379,-0.149ZM0.09,-0.104L0.09,-0.101C0.094,-0.08 0.104,-0.07 0.121,-0.07L0.13,-0.07L0.13,-0.073L0.09,-0.104ZM0.151,-0.055L0.188,-0.055L0.203,-0.058L0.203,-0.061L0.157,-0.061C0.153,-0.06 0.151,-0.058 0.151,-0.055ZM0.096,-0.058L0.096,-0.055C0.104,-0.045 0.113,-0.04 0.124,-0.04L0.13,-0.04L0.13,-0.042C0.127,-0.042 0.116,-0.048 0.096,-0.058Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/images/rounded-background.png
Normal file
|
After Width: | Height: | Size: 228 B |
BIN
assets/images/rounded-border.png
Normal file
|
After Width: | Height: | Size: 315 B |
BIN
assets/images/scrollbar-bottom.png
Normal file
|
After Width: | Height: | Size: 105 B |
17
assets/images/scrollbar-bottom.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 9 7" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,0,-10)">
|
||||
<g id="Bottom" transform="matrix(0.243243,0,0,0.179487,-59.1081,-30.2051)">
|
||||
<rect x="243" y="224" width="37" height="39" style="fill:none;"/>
|
||||
<clipPath id="_clip1">
|
||||
<rect x="243" y="224" width="37" height="39"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip1)">
|
||||
<g transform="matrix(-4.11111,6.82303e-16,-3.59619e-16,-3.97959,280,259.816)">
|
||||
<path d="M7,5.5C7,3.568 5.88,2 4.5,2C3.12,2 2,3.568 2,5.5L2,9L7,9L7,5.5Z" style="fill:white;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/images/scrollbar-middle.png
Normal file
|
After Width: | Height: | Size: 75 B |
12
assets/images/scrollbar-middle.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 9 1" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,0,-8)">
|
||||
<g id="Middle" transform="matrix(1,0,0,0.111111,0,7.11111)">
|
||||
<rect x="0" y="8" width="9" height="9" style="fill:none;"/>
|
||||
<g transform="matrix(1,0,0,9,0,-64)">
|
||||
<rect x="2" y="8" width="5" height="1" style="fill:white;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 796 B |
BIN
assets/images/scrollbar-top.png
Normal file
|
After Width: | Height: | Size: 132 B |
10
assets/images/scrollbar-top.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 9 7" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Top" transform="matrix(0.243243,0,0,0.179487,-59.1081,-40.2051)">
|
||||
<rect x="243" y="224" width="37" height="39" style="fill:none;"/>
|
||||
<g transform="matrix(4.11111,0,0,3.97959,243,227.184)">
|
||||
<path d="M7,5.5C7,3.568 5.88,2 4.5,2C3.12,2 2,3.568 2,5.5L2,9L7,9L7,5.5Z" style="fill:white;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 793 B |
BIN
assets/images/spinner-background.png
Normal file
|
After Width: | Height: | Size: 684 B |
8
assets/images/spinner-background.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Artboard1" transform="matrix(0.5,0,0,0.5,0,0)">
|
||||
<rect x="0" y="0" width="48" height="48" style="fill:none;"/>
|
||||
<path d="M24,0C37.246,0 48,10.754 48,24C48,37.246 37.246,48 24,48C10.754,48 0,37.246 0,24C0,10.754 10.754,0 24,0ZM24,8.4C32.61,8.4 39.6,15.39 39.6,24C39.6,32.61 32.61,39.6 24,39.6C15.39,39.6 8.4,32.61 8.4,24C8.4,15.39 15.39,8.4 24,8.4Z" style="fill:white;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 855 B |
BIN
assets/images/spinner-foreground.png
Normal file
|
After Width: | Height: | Size: 340 B |
8
assets/images/spinner-foreground.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Artboard1" transform="matrix(0.5,0,0,0.5,0,0)">
|
||||
<rect x="0" y="0" width="48" height="48" style="fill:none;"/>
|
||||
<path d="M48,24C48,10.745 37.255,0 24,0L24,8.4C32.616,8.4 39.6,15.384 39.6,24L48,24Z" style="fill:white;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 704 B |
@@ -35,6 +35,7 @@ fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
|
||||
|
||||
let options = BuildCommand {
|
||||
project: input,
|
||||
watch: false,
|
||||
output,
|
||||
};
|
||||
|
||||
|
||||
81
build.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use std::{
|
||||
env, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use fs_err as fs;
|
||||
use fs_err::File;
|
||||
use maplit::hashmap;
|
||||
use memofs::VfsSnapshot;
|
||||
|
||||
fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
|
||||
if path.is_dir() {
|
||||
let mut children = Vec::new();
|
||||
|
||||
for entry in fs::read_dir(path)? {
|
||||
let entry = entry?;
|
||||
|
||||
let file_name = entry.file_name().to_str().unwrap().to_owned();
|
||||
|
||||
// We can skip any TestEZ test files since they aren't necessary for
|
||||
// the plugin to run.
|
||||
if file_name.ends_with(".spec.lua") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let child_snapshot = snapshot_from_fs_path(&entry.path())?;
|
||||
children.push((file_name, child_snapshot));
|
||||
}
|
||||
|
||||
Ok(VfsSnapshot::dir(children))
|
||||
} else {
|
||||
let content = fs::read_to_string(path)?;
|
||||
|
||||
Ok(VfsSnapshot::file(content))
|
||||
}
|
||||
}
|
||||
|
||||
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 plugin_modules = plugin_root.join("modules");
|
||||
|
||||
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"))?,
|
||||
"modules" => VfsSnapshot::dir(hashmap! {
|
||||
"roact" => VfsSnapshot::dir(hashmap! {
|
||||
"src" => snapshot_from_fs_path(&plugin_modules.join("roact").join("src"))?
|
||||
}),
|
||||
"promise" => VfsSnapshot::dir(hashmap! {
|
||||
"lib" => snapshot_from_fs_path(&plugin_modules.join("promise").join("lib"))?
|
||||
}),
|
||||
"t" => VfsSnapshot::dir(hashmap! {
|
||||
"lib" => snapshot_from_fs_path(&plugin_modules.join("t").join("lib"))?
|
||||
}),
|
||||
"flipper" => VfsSnapshot::dir(hashmap! {
|
||||
"src" => snapshot_from_fs_path(&plugin_modules.join("flipper").join("src"))?
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
let out_path = Path::new(&out_dir).join("plugin.bincode");
|
||||
let out_file = File::create(&out_path)?;
|
||||
|
||||
bincode::serialize_into(out_file, &snapshot)?;
|
||||
|
||||
println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc");
|
||||
println!("cargo:rerun-if-changed=build/windows/rojo.manifest");
|
||||
embed_resource::compile("build/windows/rojo-manifest.rc");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
2
build/windows/rojo-manifest.rc
Normal file
@@ -0,0 +1,2 @@
|
||||
#define RT_MANIFEST 24
|
||||
1 RT_MANIFEST "rojo.manifest"
|
||||
8
build/windows/rojo.manifest
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||
<ws2:longPathAware>true</ws2:longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "clibrojo"
|
||||
version = "0.1.0"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rojo = { path = ".." }
|
||||
@@ -1,19 +0,0 @@
|
||||
# Rojo as a C Library
|
||||
This is an experiment to expose a C API for Rojo that would be suitable for embedding it into an existing C/C++ application.
|
||||
|
||||
I'm hoping to expand it to drop the HTTP layer and communicate through a channel, which could make it feasible to embed into an existing Roblox IDE with minimal changes or additional code.
|
||||
|
||||
## Building
|
||||
This project is currently not built by default and could break/disappear at any time.
|
||||
|
||||
```bash
|
||||
cargo build -p clibrojo
|
||||
```
|
||||
|
||||
On Windows, Cargo will generate a `clibrojo.dll` and associated `.lib` file. Link these into your project.
|
||||
|
||||
To generate the associated C header file to include in the project, use [cbindgen](https://github.com/eqrion/cbindgen):
|
||||
|
||||
```bash
|
||||
cbindgen --crate clibrojo --output include/rojo.h
|
||||
```
|
||||
@@ -1,14 +0,0 @@
|
||||
use std::{ffi::CStr, os::raw::c_char, path::PathBuf};
|
||||
|
||||
use librojo::commands::{serve, ServeOptions};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rojo_serve(path: *const c_char) {
|
||||
let path = unsafe { PathBuf::from(CStr::from_ptr(path).to_str().unwrap()) };
|
||||
|
||||
serve(&ServeOptions {
|
||||
fuzzy_project_path: path,
|
||||
port: None,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
30
design.gv
@@ -1,30 +0,0 @@
|
||||
digraph Rojo {
|
||||
concentrate = true;
|
||||
node [fontname = "sans-serif"];
|
||||
|
||||
plugin [label="Roblox Studio Plugin"]
|
||||
session [label="Session"]
|
||||
rbx_tree [label="Instance Tree"]
|
||||
imfs [label="In-Memory Filesystem"]
|
||||
fs_impl [label="Filesystem Implementation\n(stubbed in tests)"]
|
||||
fs [label="Real Filesystem"]
|
||||
snapshot_subsystem [label="Snapshot Subsystem\n(reconciler)"]
|
||||
snapshot_generator [label="Snapshot Generator"]
|
||||
user_middleware [label="User Middleware\n(MoonScript, etc.)"]
|
||||
builtin_middleware [label="Built-in Middleware\n(.lua, .rbxm, etc.)"]
|
||||
api [label="Web API"]
|
||||
file_watcher [label="File Watcher"]
|
||||
|
||||
session -> imfs
|
||||
session -> rbx_tree
|
||||
session -> snapshot_subsystem
|
||||
session -> snapshot_generator
|
||||
session -> file_watcher [dir="both"]
|
||||
file_watcher -> imfs
|
||||
snapshot_generator -> user_middleware
|
||||
snapshot_generator -> builtin_middleware
|
||||
plugin -> api [style="dotted"; dir="both"; minlen=2]
|
||||
api -> session
|
||||
imfs -> fs_impl
|
||||
fs_impl -> fs
|
||||
}
|
||||
3
foreman.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[tools]
|
||||
rojo = { source = "rojo-rbx/rojo", version = "6.1.0" }
|
||||
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
|
||||
18
memofs/CHANGELOG.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# memofs Changelog
|
||||
|
||||
## Unreleased Changes
|
||||
|
||||
## 0.2.0 (2021-08-23)
|
||||
* Updated to `crossbeam-channel` 0.5.1.
|
||||
|
||||
## 0.1.3 (2020-11-19)
|
||||
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.
|
||||
|
||||
## 0.1.2 (2020-03-29)
|
||||
* `VfsSnapshot` now implements Serde's `Serialize` and `Deserialize` traits.
|
||||
|
||||
## 0.1.1 (2020-03-18)
|
||||
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
|
||||
|
||||
## 0.1.0 (2020-03-10)
|
||||
* Initial release
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "memofs"
|
||||
description = "Virtual filesystem with configurable backends."
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
@@ -11,5 +11,7 @@ homepage = "https://github.com/rojo-rbx/rojo/tree/master/memofs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "0.5.1"
|
||||
fs-err = "2.3.0"
|
||||
notify = "4.0.15"
|
||||
crossbeam-channel = "0.4.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
@@ -19,4 +19,4 @@ memofs is currently an unstable minimum viable library. Its primary consumer is
|
||||
* Configurable caching (write-through, write-around, write-back)
|
||||
|
||||
## License
|
||||
memofs is available under the terms of the MIT license. See [LICENSE.txt](LICENSE.txt) or <https://opensource.org/licenses/MIT> for more details.
|
||||
memofs is available under the terms of the MIT license. See [LICENSE.txt](LICENSE.txt) or <https://opensource.org/licenses/MIT> for more details.
|
||||
@@ -140,13 +140,18 @@ pub enum VfsEvent {
|
||||
/// the public interfaces to this type.
|
||||
struct VfsInner {
|
||||
backend: Box<dyn VfsBackend>,
|
||||
watch_enabled: bool,
|
||||
}
|
||||
|
||||
impl VfsInner {
|
||||
fn read<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<Vec<u8>>> {
|
||||
let path = path.as_ref();
|
||||
let contents = self.backend.read(path)?;
|
||||
self.backend.watch(path)?;
|
||||
|
||||
if self.watch_enabled {
|
||||
self.backend.watch(path)?;
|
||||
}
|
||||
|
||||
Ok(Arc::new(contents))
|
||||
}
|
||||
|
||||
@@ -159,7 +164,11 @@ impl VfsInner {
|
||||
fn read_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<ReadDir> {
|
||||
let path = path.as_ref();
|
||||
let dir = self.backend.read_dir(path)?;
|
||||
self.backend.watch(path)?;
|
||||
|
||||
if self.watch_enabled {
|
||||
self.backend.watch(path)?;
|
||||
}
|
||||
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
@@ -215,6 +224,7 @@ impl Vfs {
|
||||
pub fn new<B: VfsBackend>(backend: B) -> Self {
|
||||
let lock = VfsInner {
|
||||
backend: Box::new(backend),
|
||||
watch_enabled: true,
|
||||
};
|
||||
|
||||
Self {
|
||||
@@ -229,6 +239,16 @@ impl Vfs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns automatic file watching on or off. Enabled by default.
|
||||
///
|
||||
/// Turning off file watching may be useful for single-use cases, especially
|
||||
/// on platforms like macOS where registering file watches has significant
|
||||
/// performance cost.
|
||||
pub fn set_watch_enabled(&self, enabled: bool) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.watch_enabled = enabled;
|
||||
}
|
||||
|
||||
/// Read a file from the VFS, or the underlying backend if it isn't
|
||||
/// resident.
|
||||
///
|
||||
@@ -318,6 +338,15 @@ pub struct VfsLock<'a> {
|
||||
}
|
||||
|
||||
impl VfsLock<'_> {
|
||||
/// Turns automatic file watching on or off. Enabled by default.
|
||||
///
|
||||
/// Turning off file watching may be useful for single-use cases, especially
|
||||
/// on platforms like macOS where registering file watches has significant
|
||||
/// performance cost.
|
||||
pub fn set_watch_enabled(&mut self, enabled: bool) {
|
||||
self.inner.watch_enabled = enabled;
|
||||
}
|
||||
|
||||
/// Read a file from the VFS, or the underlying backend if it isn't
|
||||
/// resident.
|
||||
///
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// A slice of a tree of files. Can be loaded into an
|
||||
/// [`InMemoryFs`](struct.InMemoryFs.html).
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum VfsSnapshot {
|
||||
File {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::sync::mpsc;
|
||||
@@ -55,15 +54,15 @@ impl StdBackend {
|
||||
|
||||
impl VfsBackend for StdBackend {
|
||||
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>> {
|
||||
fs::read(path)
|
||||
fs_err::read(path)
|
||||
}
|
||||
|
||||
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
|
||||
fs::write(path, data)
|
||||
fs_err::write(path, data)
|
||||
}
|
||||
|
||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
||||
let entries: Result<Vec<_>, _> = fs::read_dir(path)?.collect();
|
||||
let entries: Result<Vec<_>, _> = fs_err::read_dir(path)?.collect();
|
||||
let mut entries = entries?;
|
||||
|
||||
entries.sort_by_cached_key(|entry| entry.file_name());
|
||||
@@ -78,15 +77,15 @@ impl VfsBackend for StdBackend {
|
||||
}
|
||||
|
||||
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
||||
fs::remove_file(path)
|
||||
fs_err::remove_file(path)
|
||||
}
|
||||
|
||||
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
||||
fs::remove_dir_all(path)
|
||||
fs_err::remove_dir_all(path)
|
||||
}
|
||||
|
||||
fn metadata(&mut self, path: &Path) -> io::Result<Metadata> {
|
||||
let inner = fs::metadata(path)?;
|
||||
let inner = fs_err::metadata(path)?;
|
||||
|
||||
Ok(Metadata {
|
||||
is_file: inner.is_file(),
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"Fmt": {
|
||||
"$path": "fmt"
|
||||
},
|
||||
"RbxDom": {
|
||||
"$path": "rbx_dom_lua"
|
||||
},
|
||||
"Roact": {
|
||||
"$path": "modules/roact/src"
|
||||
},
|
||||
@@ -23,8 +26,8 @@
|
||||
"t": {
|
||||
"$path": "modules/t/lib"
|
||||
},
|
||||
"RbxDom": {
|
||||
"$path": "modules/rbx-dom/rbx_dom_lua/src"
|
||||
"Flipper": {
|
||||
"$path": "modules/flipper/src"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ Error.Kind = {
|
||||
},
|
||||
ConnectFailed = {
|
||||
message = "Couldn't connect to the Rojo server.\n" ..
|
||||
"Make sure the server is running -- use 'rojo serve' to run it!",
|
||||
"Make sure the server is running — use 'rojo serve' to run it!",
|
||||
},
|
||||
Timeout = {
|
||||
message = "HTTP request timed out.",
|
||||
@@ -63,4 +63,4 @@ function Error.fromRobloxErrorString(message)
|
||||
return Error.new(Error.Kind.Unknown, message)
|
||||
end
|
||||
|
||||
return Error
|
||||
return Error
|
||||
|
||||
@@ -53,4 +53,8 @@ function Log.warn(template, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Log.error(template, ...)
|
||||
error(Fmt.fmt(template, ...))
|
||||
end
|
||||
|
||||
return Log
|
||||
1
plugin/modules/flipper
Submodule
462
plugin/rbx_dom_lua/EncodedValue.lua
Normal file
@@ -0,0 +1,462 @@
|
||||
local base64 = require(script.Parent.base64)
|
||||
|
||||
local function identity(...)
|
||||
return ...
|
||||
end
|
||||
|
||||
local function unpackDecoder(f)
|
||||
return function(value)
|
||||
return f(unpack(value))
|
||||
end
|
||||
end
|
||||
|
||||
local function serializeFloat(value)
|
||||
-- TODO: Figure out a better way to serialize infinity and NaN, neither of
|
||||
-- which fit into JSON.
|
||||
if value == math.huge or value == -math.huge then
|
||||
return 999999999 * math.sign(value)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
local ALL_AXES = {"X", "Y", "Z"}
|
||||
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
|
||||
|
||||
local types
|
||||
types = {
|
||||
Axes = {
|
||||
fromPod = function(pod)
|
||||
local axes = {}
|
||||
|
||||
for index, axisName in ipairs(pod) do
|
||||
axes[index] = Enum.Axis[axisName]
|
||||
end
|
||||
|
||||
return Axes.new(unpack(axes))
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local json = {}
|
||||
|
||||
for _, axis in ipairs(ALL_AXES) do
|
||||
if roblox[axis] then
|
||||
table.insert(json, axis)
|
||||
end
|
||||
end
|
||||
|
||||
return json
|
||||
end,
|
||||
},
|
||||
|
||||
BinaryString = {
|
||||
fromPod = base64.decode,
|
||||
toPod = base64.encode,
|
||||
},
|
||||
|
||||
Bool = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
BrickColor = {
|
||||
fromPod = function(pod)
|
||||
return BrickColor.new(pod)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return roblox.Number
|
||||
end,
|
||||
},
|
||||
|
||||
CFrame = {
|
||||
fromPod = function(pod)
|
||||
local pos = pod.position
|
||||
local orient = pod.orientation
|
||||
|
||||
return CFrame.new(
|
||||
pos[1], pos[2], pos[3],
|
||||
orient[1][1], orient[1][2], orient[1][3],
|
||||
orient[2][1], orient[2][2], orient[2][3],
|
||||
orient[3][1], orient[3][2], orient[3][3]
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local x, y, z,
|
||||
r00, r01, r02,
|
||||
r10, r11, r12,
|
||||
r20, r21, r22 = roblox:GetComponents()
|
||||
|
||||
return {
|
||||
position = {x, y, z},
|
||||
orientation = {
|
||||
{r00, r01, r02},
|
||||
{r10, r11, r12},
|
||||
{r20, r21, r22},
|
||||
},
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Color3 = {
|
||||
fromPod = unpackDecoder(Color3.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.r, roblox.g, roblox.b}
|
||||
end,
|
||||
},
|
||||
|
||||
Color3uint8 = {
|
||||
fromPod = unpackDecoder(Color3.fromRGB),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
math.round(roblox.R * 255),
|
||||
math.round(roblox.G * 255),
|
||||
math.round(roblox.B * 255),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
ColorSequence = {
|
||||
fromPod = function(pod)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(pod.keypoints) do
|
||||
keypoints[index] = ColorSequenceKeypoint.new(
|
||||
keypoint.time,
|
||||
types.Color3.fromPod(keypoint.color)
|
||||
)
|
||||
end
|
||||
|
||||
return ColorSequence.new(keypoints)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(roblox.Keypoints) do
|
||||
keypoints[index] = {
|
||||
time = keypoint.Time,
|
||||
color = types.Color3.toPod(keypoint.Value),
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
keypoints = keypoints,
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Content = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
Enum = {
|
||||
fromPod = identity,
|
||||
|
||||
toPod = function(roblox)
|
||||
-- FIXME: More robust handling of enums
|
||||
if typeof(roblox) == "number" then
|
||||
return roblox
|
||||
else
|
||||
return roblox.Value
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
Faces = {
|
||||
fromPod = function(pod)
|
||||
local faces = {}
|
||||
|
||||
for index, faceName in ipairs(pod) do
|
||||
faces[index] = Enum.NormalId[faceName]
|
||||
end
|
||||
|
||||
return Faces.new(unpack(faces))
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local pod = {}
|
||||
|
||||
for _, face in ipairs(ALL_FACES) do
|
||||
if roblox[face] then
|
||||
table.insert(pod, face)
|
||||
end
|
||||
end
|
||||
|
||||
return pod
|
||||
end,
|
||||
},
|
||||
|
||||
Float32 = {
|
||||
fromPod = identity,
|
||||
toPod = serializeFloat,
|
||||
},
|
||||
|
||||
Float64 = {
|
||||
fromPod = identity,
|
||||
toPod = serializeFloat,
|
||||
},
|
||||
|
||||
Int32 = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
Int64 = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
NumberRange = {
|
||||
fromPod = unpackDecoder(NumberRange.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.Min, roblox.Max}
|
||||
end,
|
||||
},
|
||||
|
||||
NumberSequence = {
|
||||
fromPod = function(pod)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(pod.keypoints) do
|
||||
keypoints[index] = NumberSequenceKeypoint.new(
|
||||
keypoint.time,
|
||||
keypoint.value,
|
||||
keypoint.envelope
|
||||
)
|
||||
end
|
||||
|
||||
return NumberSequence.new(keypoints)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(roblox.Keypoints) do
|
||||
keypoints[index] = {
|
||||
time = keypoint.Time,
|
||||
value = keypoint.Value,
|
||||
envelope = keypoint.Envelope,
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
keypoints = keypoints,
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
PhysicalProperties = {
|
||||
fromPod = function(pod)
|
||||
if pod == "Default" then
|
||||
return nil
|
||||
else
|
||||
return PhysicalProperties.new(
|
||||
pod.density,
|
||||
pod.friction,
|
||||
pod.elasticity,
|
||||
pod.frictionWeight,
|
||||
pod.elasticityWeight
|
||||
)
|
||||
end
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
if roblox == nil then
|
||||
return "Default"
|
||||
else
|
||||
return {
|
||||
density = roblox.Density,
|
||||
friction = roblox.Friction,
|
||||
elasticity = roblox.Elasticity,
|
||||
frictionWeight = roblox.FrictionWeight,
|
||||
elasticityWeight = roblox.ElasticityWeight,
|
||||
}
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
Ray = {
|
||||
fromPod = function(pod)
|
||||
return Ray.new(
|
||||
types.Vector3.fromPod(pod.origin),
|
||||
types.Vector3.fromPod(pod.direction)
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
origin = types.Vector3.toPod(roblox.Origin),
|
||||
direction = types.Vector3.toPod(roblox.Direction),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Rect = {
|
||||
fromPod = function(pod)
|
||||
return Rect.new(
|
||||
types.Vector2.fromPod(pod[1]),
|
||||
types.Vector2.fromPod(pod[2])
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
types.Vector2.toPod(roblox.Min),
|
||||
types.Vector2.toPod(roblox.Max),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Ref = {
|
||||
fromPod = function(_pod)
|
||||
error("Ref cannot be decoded on its own")
|
||||
end,
|
||||
|
||||
toPod = function(_roblox)
|
||||
error("Ref can not be encoded on its own")
|
||||
end,
|
||||
},
|
||||
|
||||
Region3 = {
|
||||
fromPod = function(pod)
|
||||
error("Region3 is not implemented")
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
error("Region3 is not implemented")
|
||||
end,
|
||||
},
|
||||
|
||||
Region3int16 = {
|
||||
fromPod = function(pod)
|
||||
return Region3int16.new(
|
||||
types.Vector3int16.fromPod(pod[1]),
|
||||
types.Vector3int16.fromPod(pod[2])
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
types.Vector3int16.toPod(roblox.Min),
|
||||
types.Vector3int16.toPod(roblox.Max),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
SharedString = {
|
||||
fromPod = function(pod)
|
||||
error("SharedString is not supported")
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
error("SharedString is not supported")
|
||||
end,
|
||||
},
|
||||
|
||||
String = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
UDim = {
|
||||
fromPod = unpackDecoder(UDim.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.Scale, roblox.Offset}
|
||||
end,
|
||||
},
|
||||
|
||||
UDim2 = {
|
||||
fromPod = function(pod)
|
||||
return UDim2.new(
|
||||
types.UDim.fromPod(pod[1]),
|
||||
types.UDim.fromPod(pod[2])
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
types.UDim.toPod(roblox.X),
|
||||
types.UDim.toPod(roblox.Y),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Tags = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
Vector2 = {
|
||||
fromPod = unpackDecoder(Vector2.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
serializeFloat(roblox.X),
|
||||
serializeFloat(roblox.Y),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Vector2int16 = {
|
||||
fromPod = unpackDecoder(Vector2int16.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.X, roblox.Y}
|
||||
end,
|
||||
},
|
||||
|
||||
Vector3 = {
|
||||
fromPod = unpackDecoder(Vector3.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
serializeFloat(roblox.X),
|
||||
serializeFloat(roblox.Y),
|
||||
serializeFloat(roblox.Z),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Vector3int16 = {
|
||||
fromPod = unpackDecoder(Vector3int16.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.X, roblox.Y, roblox.Z}
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
local EncodedValue = {}
|
||||
|
||||
function EncodedValue.decode(encodedValue)
|
||||
local ty, value = next(encodedValue)
|
||||
|
||||
local typeImpl = types[ty]
|
||||
if typeImpl == nil then
|
||||
return false, "Couldn't decode value " .. tostring(ty)
|
||||
end
|
||||
|
||||
return true, typeImpl.fromPod(value)
|
||||
end
|
||||
|
||||
function EncodedValue.encode(rbxValue, propertyType)
|
||||
assert(propertyType ~= nil, "Property type descriptor is required")
|
||||
|
||||
local typeImpl = types[propertyType]
|
||||
if typeImpl == nil then
|
||||
return false, ("Missing encoder for property type %q"):format(propertyType)
|
||||
end
|
||||
|
||||
return true, {
|
||||
[propertyType] = typeImpl.toPod(rbxValue),
|
||||
}
|
||||
end
|
||||
|
||||
return EncodedValue
|
||||
72
plugin/rbx_dom_lua/EncodedValue.spec.lua
Normal file
@@ -0,0 +1,72 @@
|
||||
return function()
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local EncodedValue = require(script.Parent.EncodedValue)
|
||||
local allValues = require(script.Parent.allValues)
|
||||
|
||||
local function deepEq(a, b)
|
||||
if typeof(a) ~= typeof(b) then
|
||||
return false
|
||||
end
|
||||
|
||||
local ty = typeof(a)
|
||||
|
||||
if ty == "table" then
|
||||
local visited = {}
|
||||
|
||||
for key, valueA in pairs(a) do
|
||||
visited[key] = true
|
||||
|
||||
if not deepEq(valueA, b[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for key, valueB in pairs(b) do
|
||||
if visited[key] then
|
||||
continue
|
||||
end
|
||||
|
||||
if not deepEq(valueB, a[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
else
|
||||
return a == b
|
||||
end
|
||||
end
|
||||
|
||||
local extraAssertions = {
|
||||
CFrame = function(value)
|
||||
expect(value).to.equal(CFrame.new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
|
||||
end,
|
||||
}
|
||||
|
||||
for testName, testEntry in pairs(allValues) do
|
||||
it("round trip " .. testName, function()
|
||||
local ok, decoded = EncodedValue.decode(testEntry.value)
|
||||
assert(ok, decoded)
|
||||
|
||||
if extraAssertions[testName] ~= nil then
|
||||
extraAssertions[testName](decoded)
|
||||
end
|
||||
|
||||
local ok, encoded = EncodedValue.encode(decoded, testEntry.ty)
|
||||
assert(ok, encoded)
|
||||
|
||||
if not deepEq(encoded, testEntry.value) then
|
||||
local expected = HttpService:JSONEncode(testEntry.value)
|
||||
local actual = HttpService:JSONEncode(encoded)
|
||||
|
||||
local message = string.format(
|
||||
"Round-trip results did not match.\nExpected:\n%s\nActual:\n%s",
|
||||
expected, actual
|
||||
)
|
||||
|
||||
error(message)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
28
plugin/rbx_dom_lua/Error.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
local Error = {}
|
||||
Error.__index = Error
|
||||
|
||||
Error.Kind = {
|
||||
UnknownProperty = "UnknownProperty",
|
||||
PropertyNotReadable = "PropertyNotReadable",
|
||||
PropertyNotWritable = "PropertyNotWritable",
|
||||
Roblox = "Roblox",
|
||||
}
|
||||
|
||||
setmetatable(Error.Kind, {
|
||||
__index = function(_, key)
|
||||
error(("%q is not a valid member of Error.Kind"):format(tostring(key)), 2)
|
||||
end,
|
||||
})
|
||||
|
||||
function Error.new(kind, extra)
|
||||
return setmetatable({
|
||||
kind = kind,
|
||||
extra = extra,
|
||||
}, Error)
|
||||
end
|
||||
|
||||
function Error:__tostring()
|
||||
return ("Error(%s: %s)"):format(self.kind, tostring(self.extra))
|
||||
end
|
||||
|
||||
return Error
|
||||
94
plugin/rbx_dom_lua/PropertyDescriptor.lua
Normal file
@@ -0,0 +1,94 @@
|
||||
local Error = require(script.Parent.Error)
|
||||
local customProperties = require(script.Parent.customProperties)
|
||||
|
||||
-- A wrapper around a property descriptor from the reflection database with some
|
||||
-- extra convenience methods.
|
||||
--
|
||||
-- The aim of this API is to facilitate looking up a property once, then reading
|
||||
-- from it or writing to it multiple times. It's also useful when a consumer
|
||||
-- wants to check additional constraints on the property before trying to use
|
||||
-- it, like scriptability.
|
||||
local PropertyDescriptor = {}
|
||||
PropertyDescriptor.__index = PropertyDescriptor
|
||||
|
||||
local function get(container, key)
|
||||
return container[key]
|
||||
end
|
||||
|
||||
local function set(container, key, value)
|
||||
container[key] = value
|
||||
end
|
||||
|
||||
function PropertyDescriptor.fromRaw(data, className, propertyName)
|
||||
local key, value = next(data.DataType)
|
||||
|
||||
return setmetatable({
|
||||
-- The meanings of the key and value in DataType differ when the type of
|
||||
-- the property is Enum. When the property is of type Enum, the key is
|
||||
-- the name of the type:
|
||||
--
|
||||
-- { Enum = "<name of enum>" }
|
||||
--
|
||||
-- When the property is not of type Enum, the value is the name of the
|
||||
-- type:
|
||||
--
|
||||
-- { Value = "<data type>" }
|
||||
dataType = key == "Enum" and key or value,
|
||||
|
||||
scriptability = data.Scriptability,
|
||||
className = className,
|
||||
name = propertyName,
|
||||
}, PropertyDescriptor)
|
||||
end
|
||||
|
||||
function PropertyDescriptor:read(instance)
|
||||
if self.scriptability == "ReadWrite" or self.scriptability == "Read" then
|
||||
local success, value = xpcall(get, debug.traceback, instance, self.name)
|
||||
|
||||
if success then
|
||||
return success, value
|
||||
else
|
||||
return false, Error.new(Error.Kind.Roblox, value)
|
||||
end
|
||||
end
|
||||
|
||||
if self.scriptability == "Custom" then
|
||||
local interface = customProperties[self.className][self.name]
|
||||
|
||||
return interface.read(instance, self.name)
|
||||
end
|
||||
|
||||
if self.scriptability == "None" or self.scriptability == "Write" then
|
||||
local fullName = ("%s.%s"):format(instance.className, self.name)
|
||||
|
||||
return false, Error.new(Error.Kind.PropertyNotReadable, fullName)
|
||||
end
|
||||
|
||||
error(("Internal error: unexpected value of 'scriptability': %s"):format(tostring(self.scriptability)), 2)
|
||||
end
|
||||
|
||||
function PropertyDescriptor:write(instance, value)
|
||||
if self.scriptability == "ReadWrite" or self.scriptability == "Write" then
|
||||
local success, err = xpcall(set, debug.traceback, instance, self.name, value)
|
||||
|
||||
if success then
|
||||
return success
|
||||
else
|
||||
return false, Error.new(Error.Kind.Roblox, err)
|
||||
end
|
||||
end
|
||||
|
||||
if self.scriptability == "Custom" then
|
||||
local interface = customProperties[self.className][self.name]
|
||||
|
||||
return interface.write(instance, self.name, value)
|
||||
end
|
||||
|
||||
if self.scriptability == "None" or self.scriptability == "Read" then
|
||||
local fullName = ("%s.%s"):format(instance.className, self.name)
|
||||
|
||||
return false, Error.new(Error.Kind.PropertyNotWritable, fullName)
|
||||
end
|
||||
end
|
||||
|
||||
return PropertyDescriptor
|
||||
326
plugin/rbx_dom_lua/allValues.json
Normal file
@@ -0,0 +1,326 @@
|
||||
{
|
||||
"Axes": {
|
||||
"value": {
|
||||
"Axes": [
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
]
|
||||
},
|
||||
"ty": "Axes"
|
||||
},
|
||||
"BinaryString": {
|
||||
"value": {
|
||||
"BinaryString": "SGVsbG8h"
|
||||
},
|
||||
"ty": "BinaryString"
|
||||
},
|
||||
"Bool": {
|
||||
"value": {
|
||||
"Bool": true
|
||||
},
|
||||
"ty": "Bool"
|
||||
},
|
||||
"BrickColor": {
|
||||
"value": {
|
||||
"BrickColor": 1004
|
||||
},
|
||||
"ty": "BrickColor"
|
||||
},
|
||||
"CFrame": {
|
||||
"value": {
|
||||
"CFrame": {
|
||||
"position": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"orientation": [
|
||||
[
|
||||
4.0,
|
||||
5.0,
|
||||
6.0
|
||||
],
|
||||
[
|
||||
7.0,
|
||||
8.0,
|
||||
9.0
|
||||
],
|
||||
[
|
||||
10.0,
|
||||
11.0,
|
||||
12.0
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "CFrame"
|
||||
},
|
||||
"Color3": {
|
||||
"value": {
|
||||
"Color3": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
]
|
||||
},
|
||||
"ty": "Color3"
|
||||
},
|
||||
"Color3uint8": {
|
||||
"value": {
|
||||
"Color3uint8": [
|
||||
0,
|
||||
128,
|
||||
255
|
||||
]
|
||||
},
|
||||
"ty": "Color3uint8"
|
||||
},
|
||||
"ColorSequence": {
|
||||
"value": {
|
||||
"ColorSequence": {
|
||||
"keypoints": [
|
||||
{
|
||||
"time": 0.0,
|
||||
"color": [
|
||||
1.0,
|
||||
1.0,
|
||||
0.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 1.0,
|
||||
"color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "ColorSequence"
|
||||
},
|
||||
"Content": {
|
||||
"value": {
|
||||
"Content": "rbxassetid://12345"
|
||||
},
|
||||
"ty": "Content"
|
||||
},
|
||||
"Enum": {
|
||||
"value": {
|
||||
"Enum": 1234
|
||||
},
|
||||
"ty": "Enum"
|
||||
},
|
||||
"Faces": {
|
||||
"value": {
|
||||
"Faces": [
|
||||
"Right",
|
||||
"Top",
|
||||
"Back",
|
||||
"Left",
|
||||
"Bottom",
|
||||
"Front"
|
||||
]
|
||||
},
|
||||
"ty": "Faces"
|
||||
},
|
||||
"Float32": {
|
||||
"value": {
|
||||
"Float32": 15.0
|
||||
},
|
||||
"ty": "Float32"
|
||||
},
|
||||
"Float64": {
|
||||
"value": {
|
||||
"Float64": 15123.0
|
||||
},
|
||||
"ty": "Float64"
|
||||
},
|
||||
"Int32": {
|
||||
"value": {
|
||||
"Int32": 6014
|
||||
},
|
||||
"ty": "Int32"
|
||||
},
|
||||
"Int64": {
|
||||
"value": {
|
||||
"Int64": 23491023
|
||||
},
|
||||
"ty": "Int64"
|
||||
},
|
||||
"NumberRange": {
|
||||
"value": {
|
||||
"NumberRange": [
|
||||
-36.0,
|
||||
94.0
|
||||
]
|
||||
},
|
||||
"ty": "NumberRange"
|
||||
},
|
||||
"NumberSequence": {
|
||||
"value": {
|
||||
"NumberSequence": {
|
||||
"keypoints": [
|
||||
{
|
||||
"time": 0.0,
|
||||
"value": 5.0,
|
||||
"envelope": 2.0
|
||||
},
|
||||
{
|
||||
"time": 1.0,
|
||||
"value": 22.0,
|
||||
"envelope": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "NumberSequence"
|
||||
},
|
||||
"PhysicalProperties-Custom": {
|
||||
"value": {
|
||||
"PhysicalProperties": {
|
||||
"density": 0.5,
|
||||
"friction": 1.0,
|
||||
"elasticity": 0.0,
|
||||
"frictionWeight": 50.0,
|
||||
"elasticityWeight": 25.0
|
||||
}
|
||||
},
|
||||
"ty": "PhysicalProperties"
|
||||
},
|
||||
"PhysicalProperties-Default": {
|
||||
"value": {
|
||||
"PhysicalProperties": "Default"
|
||||
},
|
||||
"ty": "PhysicalProperties"
|
||||
},
|
||||
"Ray": {
|
||||
"value": {
|
||||
"Ray": {
|
||||
"origin": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"direction": [
|
||||
4.0,
|
||||
5.0,
|
||||
6.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "Ray"
|
||||
},
|
||||
"Rect": {
|
||||
"value": {
|
||||
"Rect": [
|
||||
[
|
||||
0.0,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
10.0,
|
||||
15.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"ty": "Rect"
|
||||
},
|
||||
"Region3int16": {
|
||||
"value": {
|
||||
"Region3int16": [
|
||||
[
|
||||
-10,
|
||||
-5,
|
||||
0
|
||||
],
|
||||
[
|
||||
5,
|
||||
10,
|
||||
15
|
||||
]
|
||||
]
|
||||
},
|
||||
"ty": "Region3int16"
|
||||
},
|
||||
"String": {
|
||||
"value": {
|
||||
"String": "Hello, world!"
|
||||
},
|
||||
"ty": "String"
|
||||
},
|
||||
"Tags": {
|
||||
"value": {
|
||||
"Tags": [
|
||||
"foo",
|
||||
"con'fusion?!",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"ty": "Tags"
|
||||
},
|
||||
"UDim": {
|
||||
"value": {
|
||||
"UDim": [
|
||||
1.0,
|
||||
32
|
||||
]
|
||||
},
|
||||
"ty": "UDim"
|
||||
},
|
||||
"UDim2": {
|
||||
"value": {
|
||||
"UDim2": [
|
||||
[
|
||||
-1.0,
|
||||
100
|
||||
],
|
||||
[
|
||||
1.0,
|
||||
-100
|
||||
]
|
||||
]
|
||||
},
|
||||
"ty": "UDim2"
|
||||
},
|
||||
"Vector2": {
|
||||
"value": {
|
||||
"Vector2": [
|
||||
-50.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"ty": "Vector2"
|
||||
},
|
||||
"Vector2int16": {
|
||||
"value": {
|
||||
"Vector2int16": [
|
||||
-300,
|
||||
300
|
||||
]
|
||||
},
|
||||
"ty": "Vector2int16"
|
||||
},
|
||||
"Vector3": {
|
||||
"value": {
|
||||
"Vector3": [
|
||||
-300.0,
|
||||
0.0,
|
||||
1500.0
|
||||
]
|
||||
},
|
||||
"ty": "Vector3"
|
||||
},
|
||||
"Vector3int16": {
|
||||
"value": {
|
||||
"Vector3int16": [
|
||||
60,
|
||||
37,
|
||||
-450
|
||||
]
|
||||
},
|
||||
"ty": "Vector3int16"
|
||||
}
|
||||
}
|
||||
139
plugin/rbx_dom_lua/base64.lua
Normal file
@@ -0,0 +1,139 @@
|
||||
-- Thanks to Tiffany352 for this base64 implementation!
|
||||
|
||||
local floor = math.floor
|
||||
local char = string.char
|
||||
|
||||
local function encodeBase64(str)
|
||||
local out = {}
|
||||
local nOut = 0
|
||||
local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
local strLen = #str
|
||||
|
||||
-- 3 octets become 4 hextets
|
||||
for i = 1, strLen - 2, 3 do
|
||||
local b1, b2, b3 = str:byte(i, i + 3)
|
||||
local word = b3 + b2 * 256 + b1 * 256 * 256
|
||||
|
||||
local h4 = word % 64 + 1
|
||||
word = floor(word / 64)
|
||||
local h3 = word % 64 + 1
|
||||
word = floor(word / 64)
|
||||
local h2 = word % 64 + 1
|
||||
word = floor(word / 64)
|
||||
local h1 = word % 64 + 1
|
||||
|
||||
out[nOut + 1] = alphabet:sub(h1, h1)
|
||||
out[nOut + 2] = alphabet:sub(h2, h2)
|
||||
out[nOut + 3] = alphabet:sub(h3, h3)
|
||||
out[nOut + 4] = alphabet:sub(h4, h4)
|
||||
nOut = nOut + 4
|
||||
end
|
||||
|
||||
local remainder = strLen % 3
|
||||
|
||||
if remainder == 2 then
|
||||
-- 16 input bits -> 3 hextets (2 full, 1 partial)
|
||||
local b1, b2 = str:byte(-2, -1)
|
||||
-- partial is 4 bits long, leaving 2 bits of zero padding ->
|
||||
-- offset = 4
|
||||
local word = b2 * 4 + b1 * 4 * 256
|
||||
|
||||
local h3 = word % 64 + 1
|
||||
word = floor(word / 64)
|
||||
local h2 = word % 64 + 1
|
||||
word = floor(word / 64)
|
||||
local h1 = word % 64 + 1
|
||||
|
||||
out[nOut + 1] = alphabet:sub(h1, h1)
|
||||
out[nOut + 2] = alphabet:sub(h2, h2)
|
||||
out[nOut + 3] = alphabet:sub(h3, h3)
|
||||
out[nOut + 4] = "="
|
||||
elseif remainder == 1 then
|
||||
-- 8 input bits -> 2 hextets (2 full, 1 partial)
|
||||
local b1 = str:byte(-1, -1)
|
||||
-- partial is 2 bits long, leaving 4 bits of zero padding ->
|
||||
-- offset = 16
|
||||
local word = b1 * 16
|
||||
|
||||
local h2 = word % 64 + 1
|
||||
word = floor(word / 64)
|
||||
local h1 = word % 64 + 1
|
||||
|
||||
out[nOut + 1] = alphabet:sub(h1, h1)
|
||||
out[nOut + 2] = alphabet:sub(h2, h2)
|
||||
out[nOut + 3] = "="
|
||||
out[nOut + 4] = "="
|
||||
end
|
||||
-- if the remainder is 0, then no work is needed
|
||||
|
||||
return table.concat(out, "")
|
||||
end
|
||||
|
||||
local function decodeBase64(str)
|
||||
local out = {}
|
||||
local nOut = 0
|
||||
local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
local strLen = #str
|
||||
local acc = 0
|
||||
local nAcc = 0
|
||||
|
||||
local alphabetLut = {}
|
||||
for i = 1, #alphabet do
|
||||
alphabetLut[alphabet:sub(i, i)] = i - 1
|
||||
end
|
||||
|
||||
-- 4 hextets become 3 octets
|
||||
for i = 1, strLen do
|
||||
local ch = str:sub(i, i)
|
||||
local byte = alphabetLut[ch]
|
||||
if byte then
|
||||
acc = acc * 64 + byte
|
||||
nAcc = nAcc + 1
|
||||
end
|
||||
|
||||
if nAcc == 4 then
|
||||
local b3 = acc % 256
|
||||
acc = floor(acc / 256)
|
||||
local b2 = acc % 256
|
||||
acc = floor(acc / 256)
|
||||
local b1 = acc % 256
|
||||
|
||||
out[nOut + 1] = char(b1)
|
||||
out[nOut + 2] = char(b2)
|
||||
out[nOut + 3] = char(b3)
|
||||
nOut = nOut + 3
|
||||
nAcc = 0
|
||||
acc = 0
|
||||
end
|
||||
end
|
||||
|
||||
if nAcc == 3 then
|
||||
-- 3 hextets -> 16 bit output
|
||||
acc = acc * 64
|
||||
acc = floor(acc / 256)
|
||||
local b2 = acc % 256
|
||||
acc = floor(acc / 256)
|
||||
local b1 = acc % 256
|
||||
|
||||
out[nOut + 1] = char(b1)
|
||||
out[nOut + 2] = char(b2)
|
||||
elseif nAcc == 2 then
|
||||
-- 2 hextets -> 8 bit output
|
||||
acc = acc * 64
|
||||
acc = floor(acc / 256)
|
||||
acc = acc * 64
|
||||
acc = floor(acc / 256)
|
||||
local b1 = acc % 256
|
||||
|
||||
out[nOut + 1] = char(b1)
|
||||
elseif nAcc == 1 then
|
||||
error("Base64 has invalid length")
|
||||
end
|
||||
|
||||
return table.concat(out, "")
|
||||
end
|
||||
|
||||
return {
|
||||
decode = decodeBase64,
|
||||
encode = encodeBase64,
|
||||
}
|
||||
29
plugin/rbx_dom_lua/base64.spec.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
return function()
|
||||
local base64 = require(script.Parent.base64)
|
||||
|
||||
it("should encode and decode", function()
|
||||
local function try(str, expected)
|
||||
local encoded = base64.encode(str)
|
||||
expect(encoded).to.equal(expected)
|
||||
expect(base64.decode(encoded)).to.equal(str)
|
||||
end
|
||||
|
||||
try("Man", "TWFu")
|
||||
try("Ma", "TWE=")
|
||||
try("M", "TQ==")
|
||||
try("ManM", "TWFuTQ==")
|
||||
try(
|
||||
[[Man is distinguished, not only by his reason, but by this ]]..
|
||||
[[singular passion from other animals, which is a lust of the ]]..
|
||||
[[mind, that by a perseverance of delight in the continued and ]]..
|
||||
[[indefatigable generation of knowledge, exceeds the short ]]..
|
||||
[[vehemence of any carnal pleasure.]],
|
||||
[[TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sI]]..
|
||||
[[GJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYW]]..
|
||||
[[xzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJ]]..
|
||||
[[zZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRl]]..
|
||||
[[ZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZ]]..
|
||||
[[SBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=]]
|
||||
)
|
||||
end)
|
||||
end
|
||||
44
plugin/rbx_dom_lua/customProperties.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
|
||||
-- Defines how to read and write properties that aren't directly scriptable.
|
||||
--
|
||||
-- The reflection database refers to these as having scriptability = "Custom"
|
||||
return {
|
||||
Instance = {
|
||||
Tags = {
|
||||
read = function(instance)
|
||||
return true, CollectionService:GetTags(instance)
|
||||
end,
|
||||
write = function(instance, _, value)
|
||||
local existingTags = CollectionService:GetTags(instance)
|
||||
|
||||
local unseenTags = {}
|
||||
for _, tag in ipairs(existingTags) do
|
||||
unseenTags[tag] = true
|
||||
end
|
||||
|
||||
for _, tag in ipairs(value) do
|
||||
unseenTags[tag] = nil
|
||||
CollectionService:AddTag(instance, tag)
|
||||
end
|
||||
|
||||
for tag in pairs(unseenTags) do
|
||||
CollectionService:RemoveTag(instance, tag)
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
},
|
||||
},
|
||||
LocalizationTable = {
|
||||
Contents = {
|
||||
read = function(instance, key)
|
||||
return true, instance:GetContents()
|
||||
end,
|
||||
write = function(instance, key, value)
|
||||
instance:SetContents(value)
|
||||
return true
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
48791
plugin/rbx_dom_lua/database.json
Normal file
69
plugin/rbx_dom_lua/init.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
local database = require(script.database)
|
||||
local Error = require(script.Error)
|
||||
local PropertyDescriptor = require(script.PropertyDescriptor)
|
||||
|
||||
local function findCanonicalPropertyDescriptor(className, propertyName)
|
||||
local currentClassName = className
|
||||
|
||||
repeat
|
||||
local currentClass = database.Classes[currentClassName]
|
||||
|
||||
if currentClass == nil then
|
||||
return currentClass
|
||||
end
|
||||
|
||||
local propertyData = currentClass.Properties[propertyName]
|
||||
if propertyData ~= nil then
|
||||
local canonicalData = propertyData.Kind.Canonical
|
||||
if canonicalData ~= nil then
|
||||
return PropertyDescriptor.fromRaw(propertyData, currentClassName, propertyName)
|
||||
end
|
||||
|
||||
local aliasData = propertyData.Kind.Alias
|
||||
if aliasData ~= nil then
|
||||
return PropertyDescriptor.fromRaw(
|
||||
currentClass.Properties[aliasData.AliasFor],
|
||||
currentClassName,
|
||||
aliasData.AliasFor)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
currentClassName = currentClass.Superclass
|
||||
until currentClassName == nil
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function readProperty(instance, propertyName)
|
||||
local descriptor = findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
|
||||
|
||||
if descriptor == nil then
|
||||
local fullName = ("%s.%s"):format(instance.className, propertyName)
|
||||
|
||||
return false, Error.new(Error.Kind.UnknownProperty, fullName)
|
||||
end
|
||||
|
||||
return descriptor:read(instance)
|
||||
end
|
||||
|
||||
local function writeProperty(instance, propertyName, value)
|
||||
local descriptor = findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
|
||||
|
||||
if descriptor == nil then
|
||||
local fullName = ("%s.%s"):format(instance.className, propertyName)
|
||||
|
||||
return false, Error.new(Error.Kind.UnknownProperty, fullName)
|
||||
end
|
||||
|
||||
return descriptor:write(instance, value)
|
||||
end
|
||||
|
||||
return {
|
||||
readProperty = readProperty,
|
||||
writeProperty = writeProperty,
|
||||
findCanonicalPropertyDescriptor = findCanonicalPropertyDescriptor,
|
||||
Error = Error,
|
||||
EncodedValue = require(script.EncodedValue),
|
||||
}
|
||||
7
plugin/rbx_dom_lua/init.spec.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
return function()
|
||||
local RbxDom = require(script.Parent)
|
||||
|
||||
it("should load", function()
|
||||
expect(RbxDom).to.be.ok()
|
||||
end)
|
||||
end
|
||||
@@ -233,4 +233,19 @@ function ApiContext:retrieveMessages()
|
||||
end)
|
||||
end
|
||||
|
||||
function ApiContext:open(id)
|
||||
local url = ("%s/api/open/%s"):format(self.__baseUrl, id)
|
||||
|
||||
return Http.post(url, "")
|
||||
:andThen(rejectFailedRequests)
|
||||
:andThen(Http.Response.json)
|
||||
:andThen(function(body)
|
||||
if body.sessionId ~= self.__sessionId then
|
||||
return Promise.reject("Server changed ID")
|
||||
end
|
||||
|
||||
return nil
|
||||
end)
|
||||
end
|
||||
|
||||
return ApiContext
|
||||
41
plugin/src/App/Components/BorderedContainer.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
|
||||
local SlicedImage = require(script.Parent.SlicedImage)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function BorderedContainer(props)
|
||||
return Theme.with(function(theme)
|
||||
return e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBackground,
|
||||
color = theme.BorderedContainer.BackgroundColor,
|
||||
transparency = props.transparency,
|
||||
|
||||
size = props.size,
|
||||
position = props.position,
|
||||
anchorPoint = props.anchorPoint,
|
||||
layoutOrder = props.layoutOrder,
|
||||
}, {
|
||||
Content = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, props[Roact.Children]),
|
||||
|
||||
Border = e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBorder,
|
||||
color = theme.BorderedContainer.BorderColor,
|
||||
transparency = props.transparency,
|
||||
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return BorderedContainer
|
||||
96
plugin/src/App/Components/Checkbox.lua
Normal file
@@ -0,0 +1,96 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local SlicedImage = require(script.Parent.SlicedImage)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local Checkbox = Roact.Component:extend("Checkbox")
|
||||
|
||||
function Checkbox:init()
|
||||
self.motor = Flipper.SingleMotor.new(self.props.active and 1 or 0)
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
end
|
||||
|
||||
function Checkbox:didUpdate(lastProps)
|
||||
if lastProps.active ~= self.props.active then
|
||||
self.motor:setGoal(
|
||||
Flipper.Spring.new(self.props.active and 1 or 0, {
|
||||
frequency = 6,
|
||||
dampingRatio = 1.1,
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function Checkbox:render()
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.Checkbox
|
||||
|
||||
local activeTransparency = Roact.joinBindings({
|
||||
self.binding:map(function(value)
|
||||
return 1 - value
|
||||
end),
|
||||
self.props.transparency,
|
||||
}):map(bindingUtil.blendAlpha)
|
||||
|
||||
return e("ImageButton", {
|
||||
Size = UDim2.new(0, 28, 0, 28),
|
||||
Position = self.props.position,
|
||||
AnchorPoint = self.props.anchorPoint,
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
ZIndex = self.props.zIndex,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.Activated] = self.props.onClick,
|
||||
}, {
|
||||
Active = e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBackground,
|
||||
color = theme.Active.BackgroundColor,
|
||||
transparency = activeTransparency,
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
zIndex = 2,
|
||||
}, {
|
||||
Icon = e("ImageLabel", {
|
||||
Image = Assets.Images.Checkbox.Active,
|
||||
ImageColor3 = theme.Active.IconColor,
|
||||
ImageTransparency = activeTransparency,
|
||||
|
||||
Size = UDim2.new(0, 16, 0, 16),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
}),
|
||||
|
||||
Inactive = e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBorder,
|
||||
color = theme.Inactive.BorderColor,
|
||||
transparency = self.props.transparency,
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
}, {
|
||||
Icon = e("ImageLabel", {
|
||||
Image = Assets.Images.Checkbox.Inactive,
|
||||
ImageColor3 = theme.Inactive.IconColor,
|
||||
ImageTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(0, 16, 0, 16),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return Checkbox
|
||||
55
plugin/src/App/Components/Header.lua
Normal file
@@ -0,0 +1,55 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Config = require(Plugin.Config)
|
||||
local Version = require(Plugin.Version)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function Header(props)
|
||||
return Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 32),
|
||||
LayoutOrder = props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Logo = e("ImageLabel", {
|
||||
Image = Assets.Images.Logo,
|
||||
ImageColor3 = theme.Header.LogoColor,
|
||||
ImageTransparency = props.transparency,
|
||||
|
||||
Size = UDim2.new(0, 60, 0, 27),
|
||||
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Version = e("TextLabel", {
|
||||
Text = Version.display(Config.version),
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Header.VersionColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, 14),
|
||||
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 15),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return Header
|
||||
79
plugin/src/App/Components/IconButton.lua
Normal file
@@ -0,0 +1,79 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local HOVER_SPRING_PROPS = {
|
||||
frequency = 5,
|
||||
dampingRatio = 1.1,
|
||||
}
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local IconButton = Roact.Component:extend("IconButton")
|
||||
|
||||
function IconButton:init()
|
||||
self.motor = Flipper.SingleMotor.new(0)
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
end
|
||||
|
||||
function IconButton:render()
|
||||
local iconSize = self.props.iconSize
|
||||
|
||||
return e("ImageButton", {
|
||||
Size = UDim2.new(0, iconSize * 1.5, 0, iconSize * 1.5),
|
||||
Position = self.props.position,
|
||||
AnchorPoint = self.props.anchorPoint,
|
||||
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
ZIndex = self.props.zIndex,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.Activated] = self.props.onClick,
|
||||
|
||||
[Roact.Event.MouseEnter] = function()
|
||||
self.motor:setGoal(
|
||||
Flipper.Spring.new(1, HOVER_SPRING_PROPS)
|
||||
)
|
||||
end,
|
||||
|
||||
[Roact.Event.MouseLeave] = function()
|
||||
self.motor:setGoal(
|
||||
Flipper.Spring.new(0, HOVER_SPRING_PROPS)
|
||||
)
|
||||
end,
|
||||
}, {
|
||||
Icon = e("ImageLabel", {
|
||||
Image = self.props.icon,
|
||||
ImageColor3 = self.props.color,
|
||||
ImageTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(0, iconSize, 0, iconSize),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
HoverCircle = e("ImageLabel", {
|
||||
Image = Assets.Images.Circles[128],
|
||||
ImageColor3 = self.props.color,
|
||||
ImageTransparency = Roact.joinBindings({
|
||||
hover = self.binding,
|
||||
transparency = self.props.transparency,
|
||||
}):map(function(values)
|
||||
return bindingUtil.blendAlpha({ 0.85, 1 - values.hover, values.transparency })
|
||||
end),
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return IconButton
|
||||
42
plugin/src/App/Components/ScrollingFrame.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function ScrollingFrame(props)
|
||||
return Theme.with(function(theme)
|
||||
return e("ScrollingFrame", {
|
||||
ScrollBarThickness = 9,
|
||||
ScrollBarImageColor3 = theme.ScrollBarColor,
|
||||
ScrollBarImageTransparency = props.transparency:map(function(value)
|
||||
return bindingUtil.blendAlpha({ 0.65, value })
|
||||
end),
|
||||
TopImage = Assets.Images.ScrollBar.Top,
|
||||
MidImage = Assets.Images.ScrollBar.Middle,
|
||||
BottomImage = Assets.Images.ScrollBar.Bottom,
|
||||
|
||||
ElasticBehavior = Enum.ElasticBehavior.Always,
|
||||
ScrollingDirection = Enum.ScrollingDirection.Y,
|
||||
|
||||
Size = props.size,
|
||||
Position = props.position,
|
||||
AnchorPoint = props.anchorPoint,
|
||||
CanvasSize = props.contentSize:map(function(value)
|
||||
return UDim2.new(0, 0, 0, value.Y)
|
||||
end),
|
||||
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = props[Roact.Change.AbsoluteSize]
|
||||
}, props[Roact.Children])
|
||||
end)
|
||||
end
|
||||
|
||||
return ScrollingFrame
|
||||
29
plugin/src/App/Components/SlicedImage.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function SlicedImage(props)
|
||||
local slice = props.slice
|
||||
|
||||
return e("ImageLabel", {
|
||||
Image = slice.Image,
|
||||
ImageColor3 = props.color,
|
||||
ImageTransparency = props.transparency,
|
||||
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = slice.Center,
|
||||
SliceScale = slice.Scale,
|
||||
|
||||
Size = props.size,
|
||||
Position = props.position,
|
||||
AnchorPoint = props.anchorPoint,
|
||||
|
||||
ZIndex = props.zIndex,
|
||||
LayoutOrder = props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
}, props[Roact.Children])
|
||||
end
|
||||
|
||||
return SlicedImage
|
||||
66
plugin/src/App/Components/Spinner.lua
Normal file
@@ -0,0 +1,66 @@
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
|
||||
local ROTATIONS_PER_SECOND = 1.75
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local Spinner = Roact.PureComponent:extend("Spinner")
|
||||
|
||||
function Spinner:init()
|
||||
self.rotation, self.setRotation = Roact.createBinding(0)
|
||||
end
|
||||
|
||||
function Spinner:render()
|
||||
return Theme.with(function(theme)
|
||||
return e("ImageLabel", {
|
||||
Image = Assets.Images.Spinner.Background,
|
||||
ImageColor3 = theme.Spinner.BackgroundColor,
|
||||
ImageTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(0, 24, 0, 24),
|
||||
Position = self.props.position,
|
||||
AnchorPoint = self.props.anchorPoint,
|
||||
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Foreground = e("ImageLabel", {
|
||||
Image = Assets.Images.Spinner.Foreground,
|
||||
ImageColor3 = theme.Spinner.ForegroundColor,
|
||||
ImageTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Rotation = self.rotation:map(function(value)
|
||||
return value * 360
|
||||
end),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
function Spinner:didMount()
|
||||
self.stepper = RunService.RenderStepped:Connect(function(deltaTime)
|
||||
local rotation = self.rotation:getValue()
|
||||
|
||||
rotation = rotation + deltaTime * ROTATIONS_PER_SECOND
|
||||
rotation = rotation % 1
|
||||
|
||||
self.setRotation(rotation)
|
||||
end)
|
||||
end
|
||||
|
||||
function Spinner:willUnmount()
|
||||
self.stepper:Disconnect()
|
||||
end
|
||||
|
||||
return Spinner
|
||||
7
plugin/src/App/Components/Studio/StudioPluginContext.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local StudioPluginContext = Roact.createContext(nil)
|
||||
|
||||
return StudioPluginContext
|
||||
84
plugin/src/App/Components/Studio/StudioPluginGui.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local StudioPluginContext = require(script.Parent.StudioPluginContext)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local StudioPluginGui = Roact.PureComponent:extend("StudioPluginGui")
|
||||
|
||||
StudioPluginGui.defaultProps = {
|
||||
initDockState = Enum.InitialDockState.Right,
|
||||
active = false,
|
||||
overridePreviousState = false,
|
||||
floatingSize = Vector2.new(0, 0),
|
||||
minimumSize = Vector2.new(0, 0),
|
||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
}
|
||||
|
||||
function StudioPluginGui:init()
|
||||
local floatingSize = self.props.floatingSize
|
||||
local minimumSize = self.props.minimumSize
|
||||
|
||||
local dockWidgetPluginGuiInfo = DockWidgetPluginGuiInfo.new(
|
||||
self.props.initDockState,
|
||||
self.props.active,
|
||||
self.props.overridePreviousState,
|
||||
floatingSize.X, floatingSize.Y,
|
||||
minimumSize.X, minimumSize.Y
|
||||
)
|
||||
|
||||
local pluginGui = self.props.plugin:CreateDockWidgetPluginGui(self.props.id, dockWidgetPluginGuiInfo)
|
||||
|
||||
pluginGui.Name = self.props.id
|
||||
pluginGui.Title = self.props.title
|
||||
pluginGui.ZIndexBehavior = self.props.zIndexBehavior
|
||||
|
||||
if self.props.onInitialState then
|
||||
self.props.onInitialState(pluginGui.Enabled)
|
||||
end
|
||||
|
||||
pluginGui:BindToClose(function()
|
||||
if self.props.onClose then
|
||||
self.props.onClose()
|
||||
else
|
||||
pluginGui.Enabled = false
|
||||
end
|
||||
end)
|
||||
|
||||
self.pluginGui = pluginGui
|
||||
end
|
||||
|
||||
function StudioPluginGui:render()
|
||||
return e(Roact.Portal, {
|
||||
target = self.pluginGui,
|
||||
}, self.props[Roact.Children])
|
||||
end
|
||||
|
||||
function StudioPluginGui:didUpdate(lastProps)
|
||||
if self.props.active ~= lastProps.active then
|
||||
-- This is intentionally in didUpdate to make sure the initial active state
|
||||
-- (if the PluginGui is open initially) is preserved.
|
||||
self.pluginGui.Enabled = self.props.active
|
||||
end
|
||||
end
|
||||
|
||||
function StudioPluginGui:willUnmount()
|
||||
self.pluginGui:Destroy()
|
||||
end
|
||||
|
||||
local function StudioPluginGuiWrapper(props)
|
||||
return e(StudioPluginContext.Consumer, {
|
||||
render = function(plugin)
|
||||
return e(StudioPluginGui, Dictionary.merge(props, {
|
||||
plugin = plugin,
|
||||
}))
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return StudioPluginGuiWrapper
|
||||
66
plugin/src/App/Components/Studio/StudioToggleButton.lua
Normal file
@@ -0,0 +1,66 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local StudioToolbarContext = require(script.Parent.StudioToolbarContext)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local StudioToggleButton = Roact.Component:extend("StudioToggleButton")
|
||||
|
||||
StudioToggleButton.defaultProps = {
|
||||
enabled = true,
|
||||
active = false,
|
||||
}
|
||||
|
||||
function StudioToggleButton:init()
|
||||
local button = self.props.toolbar:CreateButton(
|
||||
self.props.name,
|
||||
self.props.tooltip,
|
||||
self.props.icon,
|
||||
self.props.text
|
||||
)
|
||||
|
||||
button.Click:Connect(function()
|
||||
if self.props.onClick then
|
||||
self.props.onClick()
|
||||
end
|
||||
end)
|
||||
|
||||
button.ClickableWhenViewportHidden = true
|
||||
|
||||
self.button = button
|
||||
end
|
||||
|
||||
function StudioToggleButton:render()
|
||||
return nil
|
||||
end
|
||||
|
||||
function StudioToggleButton:didUpdate(lastProps)
|
||||
if self.props.enabled ~= lastProps.enabled then
|
||||
self.button.Enabled = self.props.enabled
|
||||
end
|
||||
|
||||
if self.props.active ~= lastProps.active then
|
||||
self.button:SetActive(self.props.active)
|
||||
end
|
||||
end
|
||||
|
||||
function StudioToggleButton:willUnmount()
|
||||
self.button:Destroy()
|
||||
end
|
||||
|
||||
local function StudioToggleButtonWrapper(props)
|
||||
return e(StudioToolbarContext.Consumer, {
|
||||
render = function(toolbar)
|
||||
return e(StudioToggleButton, Dictionary.merge(props, {
|
||||
toolbar = toolbar,
|
||||
}))
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return StudioToggleButtonWrapper
|
||||
45
plugin/src/App/Components/Studio/StudioToolbar.lua
Normal file
@@ -0,0 +1,45 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local StudioToolbarContext = require(script.Parent.StudioToolbarContext)
|
||||
local StudioPluginContext = require(script.Parent.StudioPluginContext)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local StudioToolbar = Roact.Component:extend("StudioToolbar")
|
||||
|
||||
function StudioToolbar:init()
|
||||
self.toolbar = self.props.plugin:CreateToolbar(self.props.name)
|
||||
end
|
||||
|
||||
function StudioToolbar:render()
|
||||
return e(StudioToolbarContext.Provider, {
|
||||
value = self.toolbar,
|
||||
}, self.props[Roact.Children])
|
||||
end
|
||||
|
||||
function StudioToolbar:didUpdate(lastProps)
|
||||
if self.props.name ~= lastProps.name then
|
||||
self.toolbar.Name = self.props.name
|
||||
end
|
||||
end
|
||||
|
||||
function StudioToolbar:willUnmount()
|
||||
self.toolbar:Destroy()
|
||||
end
|
||||
|
||||
local function StudioToolbarWrapper(props)
|
||||
return e(StudioPluginContext.Consumer, {
|
||||
render = function(plugin)
|
||||
return e(StudioToolbar, Dictionary.merge(props, {
|
||||
plugin = plugin,
|
||||
}))
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return StudioToolbarWrapper
|
||||
@@ -0,0 +1,7 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local StudioToolbarContext = Roact.createContext(nil)
|
||||
|
||||
return StudioToolbarContext
|
||||
137
plugin/src/App/Components/TextButton.lua
Normal file
@@ -0,0 +1,137 @@
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local SlicedImage = require(script.Parent.SlicedImage)
|
||||
local TouchRipple = require(script.Parent.TouchRipple)
|
||||
|
||||
local SPRING_PROPS = {
|
||||
frequency = 5,
|
||||
dampingRatio = 1,
|
||||
}
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local TextButton = Roact.Component:extend("TextButton")
|
||||
|
||||
function TextButton:init()
|
||||
self.motor = Flipper.GroupMotor.new({
|
||||
hover = 0,
|
||||
enabled = self.props.enabled and 1 or 0,
|
||||
})
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
end
|
||||
|
||||
function TextButton:didUpdate(lastProps)
|
||||
if lastProps.enabled ~= self.props.enabled then
|
||||
self.motor:setGoal({
|
||||
enabled = Flipper.Spring.new(self.props.enabled and 1 or 0),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function TextButton:render()
|
||||
return Theme.with(function(theme)
|
||||
local textSize = TextService:GetTextSize(
|
||||
self.props.text, 18, Enum.Font.GothamSemibold,
|
||||
Vector2.new(math.huge, math.huge)
|
||||
)
|
||||
|
||||
local style = self.props.style
|
||||
|
||||
theme = 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),
|
||||
Position = self.props.position,
|
||||
AnchorPoint = self.props.anchorPoint,
|
||||
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.Activated] = self.props.onClick,
|
||||
|
||||
[Roact.Event.MouseEnter] = function()
|
||||
self.motor:setGoal({
|
||||
hover = Flipper.Spring.new(1, SPRING_PROPS),
|
||||
})
|
||||
end,
|
||||
|
||||
[Roact.Event.MouseLeave] = function()
|
||||
self.motor:setGoal({
|
||||
hover = Flipper.Spring.new(0, SPRING_PROPS),
|
||||
})
|
||||
end,
|
||||
}, {
|
||||
TouchRipple = e(TouchRipple, {
|
||||
color = theme.ActionFillColor,
|
||||
transparency = self.props.transparency:map(function(value)
|
||||
return bindingUtil.blendAlpha({ theme.ActionFillTransparency, value })
|
||||
end),
|
||||
zIndex = 2,
|
||||
}),
|
||||
|
||||
Text = e("TextLabel", {
|
||||
Text = self.props.text,
|
||||
Font = Enum.Font.GothamSemibold,
|
||||
TextSize = 18,
|
||||
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
|
||||
TextTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Border = style == "Bordered" and e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBorder,
|
||||
color = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.BorderColor, theme.Disabled.BorderColor),
|
||||
transparency = self.props.transparency,
|
||||
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
zIndex = 0,
|
||||
}),
|
||||
|
||||
HoverOverlay = e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBackground,
|
||||
color = theme.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 })
|
||||
end),
|
||||
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
zIndex = -1,
|
||||
}),
|
||||
|
||||
Background = style == "Solid" and e(SlicedImage, {
|
||||
slice = Assets.Slices.RoundedBackground,
|
||||
color = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.BackgroundColor, theme.Disabled.BackgroundColor),
|
||||
transparency = self.props.transparency,
|
||||
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
zIndex = -2,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return TextButton
|
||||
145
plugin/src/App/Components/TouchRipple.lua
Normal file
@@ -0,0 +1,145 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
|
||||
local EXPAND_SPRING = {
|
||||
frequency = 7,
|
||||
dampingRatio = 2,
|
||||
}
|
||||
|
||||
local TouchRipple = Roact.Component:extend("TouchRipple")
|
||||
|
||||
function TouchRipple:init()
|
||||
self.ref = Roact.createRef()
|
||||
|
||||
self.motor = Flipper.GroupMotor.new({
|
||||
scale = 0,
|
||||
opacity = 0,
|
||||
})
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
|
||||
self.position, self.setPosition = Roact.createBinding(
|
||||
Vector2.new(0, 0)
|
||||
)
|
||||
end
|
||||
|
||||
function TouchRipple:reset()
|
||||
self.motor:setGoal({
|
||||
scale = Flipper.Instant.new(0),
|
||||
opacity = Flipper.Instant.new(0),
|
||||
})
|
||||
|
||||
-- Forces motor to update
|
||||
self.motor:step(0)
|
||||
end
|
||||
|
||||
function TouchRipple:calculateRadius(position)
|
||||
local container = self.ref.current
|
||||
|
||||
if container then
|
||||
local corner = Vector2.new(
|
||||
math.floor((1 - position.X) + 0.5),
|
||||
math.floor((1 - position.Y) + 0.5)
|
||||
)
|
||||
|
||||
local size = container.AbsoluteSize
|
||||
local ratio = size / math.min(size.X, size.Y)
|
||||
|
||||
return ((corner * ratio) - (position * ratio)).Magnitude
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function TouchRipple:render()
|
||||
local scale = bindingUtil.deriveProperty(self.binding, "scale")
|
||||
local transparency = bindingUtil.deriveProperty(self.binding, "opacity"):map(function(value)
|
||||
return 1 - value
|
||||
end)
|
||||
|
||||
transparency = Roact.joinBindings({
|
||||
transparency,
|
||||
self.props.transparency,
|
||||
}):map(bindingUtil.blendAlpha)
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
ClipsDescendants = true,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
ZIndex = self.props.zIndex,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = self.ref,
|
||||
|
||||
[Roact.Event.InputBegan] = function(object, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
self:reset()
|
||||
|
||||
local position = Vector2.new(input.Position.X, input.Position.Y)
|
||||
local relativePosition = (position - object.AbsolutePosition) / object.AbsoluteSize
|
||||
|
||||
self.setPosition(relativePosition)
|
||||
|
||||
self.motor:setGoal({
|
||||
scale = Flipper.Spring.new(1, EXPAND_SPRING),
|
||||
opacity = Flipper.Spring.new(1, EXPAND_SPRING),
|
||||
})
|
||||
|
||||
input:GetPropertyChangedSignal("UserInputState"):Connect(function()
|
||||
local userInputState = input.UserInputState
|
||||
|
||||
if
|
||||
userInputState == Enum.UserInputState.Cancel
|
||||
or userInputState == Enum.UserInputState.End
|
||||
then
|
||||
self.motor:setGoal({
|
||||
opacity = Flipper.Spring.new(0, {
|
||||
frequency = 5,
|
||||
dampingRatio = 1,
|
||||
}),
|
||||
})
|
||||
end
|
||||
end)
|
||||
end
|
||||
end,
|
||||
}, {
|
||||
Circle = Roact.createElement("ImageLabel", {
|
||||
Image = Assets.Images.Circles[500],
|
||||
ImageColor3 = self.props.color,
|
||||
ImageTransparency = transparency,
|
||||
|
||||
Size = Roact.joinBindings({
|
||||
scale = scale,
|
||||
position = self.position,
|
||||
}):map(function(values)
|
||||
local targetSize = self:calculateRadius(values.position) * 2
|
||||
local currentSize = targetSize * values.scale
|
||||
|
||||
local container = self.ref.current
|
||||
|
||||
if container then
|
||||
local containerSize = container.AbsoluteSize
|
||||
local containerAspect = containerSize.X / containerSize.Y
|
||||
|
||||
return UDim2.new(
|
||||
currentSize / math.max(containerAspect, 1), 0,
|
||||
currentSize * math.min(containerAspect, 1), 0
|
||||
)
|
||||
end
|
||||
end),
|
||||
|
||||
Position = self.position:map(function(value)
|
||||
return UDim2.new(value.X, 0, value.Y, 0)
|
||||
end),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return TouchRipple
|
||||
70
plugin/src/App/Page.lua
Normal file
@@ -0,0 +1,70 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local bindingUtil = require(script.Parent.bindingUtil)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local Page = Roact.Component:extend("Page")
|
||||
|
||||
function Page:init()
|
||||
self:setState({
|
||||
rendered = self.props.active
|
||||
})
|
||||
|
||||
self.motor = Flipper.SingleMotor.new(self.props.active and 1 or 0)
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
|
||||
self.motor:onStep(function(value)
|
||||
local rendered = value > 0
|
||||
|
||||
self:setState(function(state)
|
||||
if state.rendered ~= rendered then
|
||||
return {
|
||||
rendered = rendered,
|
||||
}
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function Page:render()
|
||||
if not self.state.rendered then
|
||||
return nil
|
||||
end
|
||||
|
||||
local transparency = self.binding:map(function(value)
|
||||
return 1 - value
|
||||
end)
|
||||
|
||||
return e("Frame", {
|
||||
Position = transparency:map(function(value)
|
||||
value = self.props.active and value or -value
|
||||
return UDim2.new(0, value * 30, 0, 0)
|
||||
end),
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Component = e(self.props.component, Dictionary.merge(self.props, {
|
||||
transparency = transparency,
|
||||
}))
|
||||
})
|
||||
end
|
||||
|
||||
function Page:didUpdate(lastProps)
|
||||
if self.props.active ~= lastProps.active then
|
||||
self.motor:setGoal(
|
||||
Flipper.Spring.new(self.props.active and 1 or 0, {
|
||||
frequency = 6,
|
||||
dampingRatio = 1,
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return Page
|
||||
121
plugin/src/App/PluginSettings.lua
Normal file
@@ -0,0 +1,121 @@
|
||||
--[[
|
||||
Persistent plugin settings that can be accessed via Roact context.
|
||||
]]
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local defaultSettings = {
|
||||
openScriptsExternally = false,
|
||||
twoWaySync = false,
|
||||
}
|
||||
|
||||
local Settings = {}
|
||||
Settings.__index = Settings
|
||||
|
||||
function Settings.fromPlugin(plugin)
|
||||
local values = {}
|
||||
|
||||
for name, defaultValue in pairs(defaultSettings) do
|
||||
local savedValue = plugin:GetSetting("Rojo_" .. name)
|
||||
|
||||
if savedValue == nil then
|
||||
plugin:SetSetting("Rojo_" .. name, defaultValue)
|
||||
values[name] = defaultValue
|
||||
else
|
||||
values[name] = savedValue
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
__values = values,
|
||||
__plugin = plugin,
|
||||
__updateListeners = {},
|
||||
}, Settings)
|
||||
end
|
||||
|
||||
function Settings:get(name)
|
||||
if defaultSettings[name] == nil then
|
||||
error("Invalid setings name " .. tostring(name), 2)
|
||||
end
|
||||
|
||||
return self.__values[name]
|
||||
end
|
||||
|
||||
function Settings:set(name, value)
|
||||
self.__plugin:SetSetting("Rojo_" .. name, value)
|
||||
self.__values[name] = value
|
||||
|
||||
for callback in pairs(self.__updateListeners) do
|
||||
callback(name, value)
|
||||
end
|
||||
end
|
||||
|
||||
function Settings:onUpdate(newCallback)
|
||||
local newListeners = {}
|
||||
for callback in pairs(self.__updateListeners) do
|
||||
newListeners[callback] = true
|
||||
end
|
||||
|
||||
newListeners[newCallback] = true
|
||||
self.__updateListeners = newListeners
|
||||
|
||||
return function()
|
||||
local newListeners = {}
|
||||
for callback in pairs(self.__updateListeners) do
|
||||
if callback ~= newCallback then
|
||||
newListeners[callback] = true
|
||||
end
|
||||
end
|
||||
|
||||
self.__updateListeners = newListeners
|
||||
end
|
||||
end
|
||||
|
||||
local Context = Roact.createContext(nil)
|
||||
|
||||
local StudioProvider = Roact.Component:extend("StudioProvider")
|
||||
|
||||
function StudioProvider:init()
|
||||
self.settings = Settings.fromPlugin(self.props.plugin)
|
||||
end
|
||||
|
||||
function StudioProvider:render()
|
||||
return Roact.createElement(Context.Provider, {
|
||||
value = self.settings,
|
||||
}, self.props[Roact.Children])
|
||||
end
|
||||
|
||||
local InternalConsumer = Roact.Component:extend("InternalConsumer")
|
||||
|
||||
function InternalConsumer:render()
|
||||
return self.props.render(self.props.settings)
|
||||
end
|
||||
|
||||
function InternalConsumer:didMount()
|
||||
self.disconnect = self.props.settings:onUpdate(function()
|
||||
-- Trigger a dummy state update to update the settings consumer.
|
||||
self:setState({})
|
||||
end)
|
||||
end
|
||||
|
||||
function InternalConsumer:willUnmount()
|
||||
self.disconnect()
|
||||
end
|
||||
|
||||
local function with(callback)
|
||||
return Roact.createElement(Context.Consumer, {
|
||||
render = function(settings)
|
||||
return Roact.createElement(InternalConsumer, {
|
||||
settings = settings,
|
||||
render = callback,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return {
|
||||
StudioProvider = StudioProvider,
|
||||
with = with,
|
||||
}
|
||||
125
plugin/src/App/StatusPages/Connected.lua
Normal file
@@ -0,0 +1,125 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
|
||||
local Header = require(Plugin.App.Components.Header)
|
||||
local IconButton = require(Plugin.App.Components.IconButton)
|
||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function ConnectionDetails(props)
|
||||
return Theme.with(function(theme)
|
||||
return e(BorderedContainer, {
|
||||
transparency = props.transparency,
|
||||
size = UDim2.new(1, 0, 0, 70),
|
||||
layoutOrder = props.layoutOrder,
|
||||
}, {
|
||||
TextContainer = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1
|
||||
}, {
|
||||
ProjectName = e("TextLabel", {
|
||||
Text = props.projectName,
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 20,
|
||||
TextColor3 = theme.ConnectionDetails.ProjectNameColor,
|
||||
TextTransparency = props.transparency,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, 20),
|
||||
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Address = e("TextLabel", {
|
||||
Text = props.address,
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 15,
|
||||
TextColor3 = theme.ConnectionDetails.AddressColor,
|
||||
TextTransparency = props.transparency,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, 15),
|
||||
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 6),
|
||||
}),
|
||||
}),
|
||||
|
||||
Disconnect = e(IconButton, {
|
||||
icon = Assets.Images.Icons.Close,
|
||||
iconSize = 24,
|
||||
color = theme.ConnectionDetails.DisconnectColor,
|
||||
transparency = props.transparency,
|
||||
|
||||
position = UDim2.new(1, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(1, 0.5),
|
||||
|
||||
onClick = props.onDisconnect,
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 17),
|
||||
PaddingRight = UDim.new(0, 15),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local ConnectedPage = Roact.Component:extend("ConnectedPage")
|
||||
|
||||
function ConnectedPage:render()
|
||||
return Roact.createFragment({
|
||||
Header = e(Header, {
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
ConnectionDetails = e(ConnectionDetails, {
|
||||
projectName = self.state.projectName,
|
||||
address = self.state.address,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
|
||||
onDisconnect = self.props.onDisconnect,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 20),
|
||||
PaddingRight = UDim.new(0, 20),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
function ConnectedPage.getDerivedStateFromProps(props)
|
||||
-- If projectName or address ever get removed from props, make sure we still have
|
||||
-- the properties! The component still needs to have its data for it to be properly
|
||||
-- animated out without the labels changing.
|
||||
|
||||
return {
|
||||
projectName = props.projectName,
|
||||
address = props.address,
|
||||
}
|
||||
end
|
||||
|
||||
return ConnectedPage
|
||||
20
plugin/src/App/StatusPages/Connecting.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Spinner = require(Plugin.App.Components.Spinner)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local ConnectingPage = Roact.Component:extend("ConnectingPage")
|
||||
|
||||
function ConnectingPage:render()
|
||||
return e(Spinner, {
|
||||
position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(0.5, 0.5),
|
||||
transparency = self.props.transparency,
|
||||
})
|
||||
end
|
||||
|
||||
return ConnectingPage
|
||||
153
plugin/src/App/StatusPages/Error.lua
Normal file
@@ -0,0 +1,153 @@
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
|
||||
local TextButton = require(Plugin.App.Components.TextButton)
|
||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local ERROR_PADDING = Vector2.new(13, 10)
|
||||
|
||||
local Error = Roact.Component:extend("Error")
|
||||
|
||||
function Error:init()
|
||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
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
|
||||
|
||||
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
|
||||
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("TextLabel", {
|
||||
Text = self.props.errorMessage,
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 16,
|
||||
TextColor3 = theme.ErrorColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
TextTransparency = self.props.transparency,
|
||||
TextWrapped = true,
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
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),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local ErrorPage = Roact.Component:extend("ErrorPage")
|
||||
|
||||
function ErrorPage:init()
|
||||
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
end
|
||||
|
||||
function ErrorPage:render()
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(object)
|
||||
self.setContainerSize(object.AbsoluteSize)
|
||||
end,
|
||||
}, {
|
||||
Error = e(Error, {
|
||||
errorMessage = self.state.errorMessage,
|
||||
containerSize = self.containerSize,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
Buttons = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 35),
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Close = e(TextButton, {
|
||||
text = "Okay",
|
||||
style = "Bordered",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
onClick = self.props.onClose,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 14),
|
||||
PaddingRight = UDim.new(0, 14),
|
||||
PaddingTop = UDim.new(0, 14),
|
||||
PaddingBottom = UDim.new(0, 14),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
function ErrorPage.getDerivedStateFromProps(props)
|
||||
-- If errorMessage ever gets removed from props, make sure we still have the
|
||||
-- property! The component still needs to have its data for it to be properly
|
||||
-- animated out without the labels changing.
|
||||
|
||||
return {
|
||||
errorMessage = props.errorMessage,
|
||||
}
|
||||
end
|
||||
|
||||
return ErrorPage
|
||||
159
plugin/src/App/StatusPages/NotConnected.lua
Normal file
@@ -0,0 +1,159 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Config = require(Plugin.Config)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
local TextButton = require(Plugin.App.Components.TextButton)
|
||||
local Header = require(Plugin.App.Components.Header)
|
||||
|
||||
local PORT_WIDTH = 74
|
||||
local DIVIDER_WIDTH = 1
|
||||
local HOST_OFFSET = 12
|
||||
|
||||
local lastHost, lastPort
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local function AddressEntry(props)
|
||||
return Theme.with(function(theme)
|
||||
return e(BorderedContainer, {
|
||||
transparency = props.transparency,
|
||||
size = UDim2.new(1, 0, 0, 36),
|
||||
layoutOrder = props.layoutOrder,
|
||||
}, {
|
||||
Host = e("TextBox", {
|
||||
Text = lastHost or "",
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.AddressEntry.TextColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = props.transparency,
|
||||
PlaceholderText = Config.defaultHost,
|
||||
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
|
||||
Size = UDim2.new(1, -(HOST_OFFSET + DIVIDER_WIDTH + PORT_WIDTH), 1, 0),
|
||||
Position = UDim2.new(0, HOST_OFFSET, 0, 0),
|
||||
|
||||
ClipsDescendants = true,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = props.hostRef,
|
||||
}),
|
||||
|
||||
Port = e("TextBox", {
|
||||
Text = lastPort or "",
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.AddressEntry.TextColor,
|
||||
TextTransparency = props.transparency,
|
||||
PlaceholderText = Config.defaultPort,
|
||||
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
|
||||
Size = UDim2.new(0, PORT_WIDTH, 1, 0),
|
||||
Position = UDim2.new(1, 0, 0, 0),
|
||||
AnchorPoint = Vector2.new(1, 0),
|
||||
|
||||
ClipsDescendants = true,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = props.portRef,
|
||||
|
||||
[Roact.Change.Text] = function(object)
|
||||
local text = object.Text
|
||||
text = text:gsub("%D", "")
|
||||
object.Text = text
|
||||
end,
|
||||
}, {
|
||||
Divider = e("Frame", {
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||
BackgroundTransparency = props.transparency,
|
||||
Size = UDim2.new(0, DIVIDER_WIDTH, 1, 0),
|
||||
AnchorPoint = Vector2.new(1, 0),
|
||||
BorderSizePixel = 0,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local NotConnectedPage = Roact.Component:extend("NotConnectedPage")
|
||||
|
||||
function NotConnectedPage:init()
|
||||
self.hostRef = Roact.createRef()
|
||||
self.portRef = Roact.createRef()
|
||||
end
|
||||
|
||||
function NotConnectedPage:render()
|
||||
return Roact.createFragment({
|
||||
Header = e(Header, {
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
AddressEntry = e(AddressEntry, {
|
||||
hostRef = self.hostRef,
|
||||
portRef = self.portRef,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
|
||||
Buttons = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 34),
|
||||
LayoutOrder = 3,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Settings = e(TextButton, {
|
||||
text = "Settings",
|
||||
style = "Bordered",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
onClick = self.props.onNavigateSettings,
|
||||
}),
|
||||
|
||||
Connect = e(TextButton, {
|
||||
text = "Connect",
|
||||
style = "Solid",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
onClick = function()
|
||||
local hostText = self.hostRef.current.Text
|
||||
local portText = self.portRef.current.Text
|
||||
|
||||
lastHost = hostText
|
||||
lastPort = portText
|
||||
|
||||
self.props.onConnect(
|
||||
#hostText > 0 and hostText or Config.defaultHost,
|
||||
#portText > 0 and portText or Config.defaultPort
|
||||
)
|
||||
end,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 20),
|
||||
PaddingRight = UDim.new(0, 20),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return NotConnectedPage
|
||||
230
plugin/src/App/StatusPages/Settings.lua
Normal file
@@ -0,0 +1,230 @@
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local PluginSettings = require(Plugin.App.PluginSettings)
|
||||
|
||||
local Checkbox = require(Plugin.App.Components.Checkbox)
|
||||
local IconButton = require(Plugin.App.Components.IconButton)
|
||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local DIVIDER_FADE_SIZE = 0.1
|
||||
|
||||
local function getTextBounds(text, textSize, font, lineHeight, bounds)
|
||||
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
|
||||
|
||||
local lineCount = textBounds.Y / textSize
|
||||
local lineHeightAbsolute = textSize * lineHeight
|
||||
|
||||
return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
|
||||
end
|
||||
|
||||
local function Navbar(props)
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.Settings.Navbar
|
||||
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 46),
|
||||
LayoutOrder = props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Back = e(IconButton, {
|
||||
icon = Assets.Images.Icons.Back,
|
||||
iconSize = 24,
|
||||
color = theme.BackButtonColor,
|
||||
transparency = props.transparency,
|
||||
|
||||
position = UDim2.new(0, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(0, 0.5),
|
||||
|
||||
onClick = props.onBack,
|
||||
}),
|
||||
|
||||
Text = e("TextLabel", {
|
||||
Text = "Settings",
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.TextColor,
|
||||
TextTransparency = props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local Setting = Roact.Component:extend("Setting")
|
||||
|
||||
function Setting:init()
|
||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
end
|
||||
|
||||
function Setting:render()
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.Settings
|
||||
|
||||
return PluginSettings.with(function(settings)
|
||||
return e("Frame", {
|
||||
Size = self.contentSize:map(function(value)
|
||||
return UDim2.new(1, 0, 0, 20 + value.Y + 20)
|
||||
end),
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Change.AbsoluteSize] = function(object)
|
||||
self.setContainerSize(object.AbsoluteSize)
|
||||
end,
|
||||
}, {
|
||||
Checkbox = e(Checkbox, {
|
||||
active = settings:get(self.props.id),
|
||||
transparency = self.props.transparency,
|
||||
position = UDim2.new(1, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(1, 0.5),
|
||||
onClick = function()
|
||||
local currentValue = settings:get(self.props.id)
|
||||
settings:set(self.props.id, not currentValue)
|
||||
end,
|
||||
}),
|
||||
|
||||
Text = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Name = e("TextLabel", {
|
||||
Text = self.props.name,
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 17,
|
||||
TextColor3 = theme.Setting.NameColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, 17),
|
||||
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Description = e("TextLabel", {
|
||||
Text = self.props.description,
|
||||
Font = Enum.Font.Gotham,
|
||||
LineHeight = 1.2,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Setting.DescriptionColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
TextWrapped = true,
|
||||
|
||||
Size = self.containerSize:map(function(value)
|
||||
local textBounds = getTextBounds(
|
||||
self.props.description, 14, Enum.Font.Gotham, 1.2,
|
||||
Vector2.new(value.X - 50, math.huge)
|
||||
)
|
||||
return UDim2.new(1, -50, 0, textBounds.Y)
|
||||
end),
|
||||
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 6),
|
||||
|
||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
||||
self.setContentSize(object.AbsoluteContentSize)
|
||||
end,
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingTop = UDim.new(0, 20),
|
||||
PaddingBottom = UDim.new(0, 20),
|
||||
}),
|
||||
}),
|
||||
|
||||
Divider = e("Frame", {
|
||||
BackgroundColor3 = theme.DividerColor,
|
||||
BackgroundTransparency = self.props.transparency,
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
BorderSizePixel = 0,
|
||||
}, {
|
||||
Gradient = e("UIGradient", {
|
||||
Transparency = NumberSequence.new({
|
||||
NumberSequenceKeypoint.new(0, 1),
|
||||
NumberSequenceKeypoint.new(DIVIDER_FADE_SIZE, 0),
|
||||
NumberSequenceKeypoint.new(1 - DIVIDER_FADE_SIZE, 0),
|
||||
NumberSequenceKeypoint.new(1, 1),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
local SettingsPage = Roact.Component:extend("SettingsPage")
|
||||
|
||||
function SettingsPage:init()
|
||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||
end
|
||||
|
||||
function SettingsPage:render()
|
||||
return Theme.with(function(theme)
|
||||
theme = theme.Settings
|
||||
|
||||
return e(ScrollingFrame, {
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
contentSize = self.contentSize,
|
||||
transparency = self.props.transparency,
|
||||
}, {
|
||||
Navbar = e(Navbar, {
|
||||
onBack = self.props.onBack,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 0,
|
||||
}),
|
||||
|
||||
OpenScriptsExternally = e(Setting, {
|
||||
id = "openScriptsExternally",
|
||||
name = "Open Scripts Externally",
|
||||
description = "Attempt to open scripts in an external editor",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
TwoWaySync = e(Setting, {
|
||||
id = "twoWaySync",
|
||||
name = "Two-Way Sync",
|
||||
description = "EXPERIMENTAL! Editing files in Studio will sync them into the filesystem",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
|
||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
||||
self.setContentSize(object.AbsoluteContentSize)
|
||||
end,
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 20),
|
||||
PaddingRight = UDim.new(0, 20),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return SettingsPage
|
||||