Compare commits

...

115 Commits

Author SHA1 Message Date
Lucien Greathouse
b84aab0960 Release v6.0.2 2021-02-09 11:45:58 -05:00
Lucien Greathouse
0e89b91c38 Implement CSRF challenge support in upload 2021-02-09 11:36:59 -05:00
Lucien Greathouse
7888a704e1 Release 6.0.1 2021-01-22 13:47:39 -07:00
Lucien Greathouse
804fd3de8e Remove Requester header to handle API change 2021-01-22 13:41:55 -07:00
Lucien Greathouse
4992c36f08 Delete ErrorDisplay, use anyhow instead! 2021-01-18 14:54:12 -07:00
Lucien Greathouse
27af0c841b Release 6.0.0 2021-01-16 18:35:27 -07:00
Lucien Greathouse
cc4f4df4f9 Support changing ClassName in Reconciler.diff 2021-01-16 18:26:30 -07:00
Lucien Greathouse
5989ab3b85 Add project to test that plugin skips unwritable properties 2021-01-16 15:34:59 -07:00
Lucien Greathouse
040b9c1452 Delete accidentally committed test bash script 2021-01-14 11:55:24 -07:00
Lucien Greathouse
bb7bd2e27e plugin: Update reflection database 2021-01-13 23:31:57 -07:00
tacheometry
02dbd4ba75 fix hyphen (#378)
This uses an em dash instead of double lines (--)
2021-01-06 13:49:05 -07:00
Lucien Greathouse
3b149cc875 Drop SnapshotError in favor of anyhow::Error 2020-12-18 12:16:05 -08:00
Lucien Greathouse
f3745c68d2 Turn rbxm/rbxmx errors into error variants 2020-12-18 11:39:41 -08:00
Lucien Greathouse
eddbe7d0cf Release 6.0.0-rc.4 2020-12-14 14:15:09 -08:00
Lucien Greathouse
faf86d006a Remove outdated warning from Rojo 0.5.0 alpha 14 2020-12-14 13:16:34 -08:00
Lucien Greathouse
71e4dfeb14 Change logo to be brand color on dark theme 2020-12-14 12:59:53 -08:00
Lucien Greathouse
da8ed6ddf9 Update Changelog 2020-12-14 11:22:43 -08:00
Lucien Greathouse
503e687c55 Fix repository link 2020-12-14 11:20:28 -08:00
Lucien Greathouse
dd667cce0b Add images from plugin rewrite 2020-12-14 11:20:07 -08:00
Lucien Greathouse
f911009752 Remove clibrojo, as it's broken 2020-12-14 11:13:44 -08:00
Reselim
cae4c46669 New UI (#367)
* Add Flipper

* Remove old UI

* Add boilerplate UI

* Change plugin version

* Merge upstream

* Bunch of new UI changes

Too lazy to list them all in individual commits

* Touch ripple for buttons and a few other things

* Make the close button on the PluginGui work

* Set button state to guiEnabled

* Implement Connecting, NotConnected; add Header; don't update plugin button on render

* Replace mapLerpColor with mapLerp

* Update blendAlpha to be 0 without any values

* Add ActionFillTransparency to Theme.Button

* Suffix all Theme entries

* Update Flipper

* Add disconnect button

* Remove cancel button

* Add settings page

* Add scrollbar and dark theme support to settings

* Include settings in startSession

* Set context default value to nil

I always thought this was the name, lol...

* Add Error page

* Fix preloadAssets

* Fix preloadAssets import

* Update checkbox colors a little

* Add setting descriptions

* Fix scrolling frame in settings panel

* Remove .vscode

* Rename Throbber to Spinner

* Update merge

* Move Spinner images to assets

* Change casing of directories

* Remove old directories

* Add comments to getDerivedStateFromProps

* Account for offset in host TextBox size

* Turn width variables into constants

* Attempt to fix the comments

* Add a missing comma in Settings

* Remove a double space

* Remove Dummy object

* Move most of the Studio logic out of render

* Don't truncate port input

* Replace merge with Dictionary.merge

* Replace "Got it!" with "Okay"

* Add projectName to setStatus call

* Add Flipper to build.rs
2020-12-14 11:07:39 -08:00
Lucien Greathouse
a937fc38db Update Changelog 2020-12-14 10:22:45 -08:00
Lucien Greathouse
ff43ffce07 Update to insta 1.3.0 2020-12-14 10:22:40 -08:00
Lucien Greathouse
5bb3dc258a Add projectName to serve output 2020-12-14 10:17:52 -08:00
Lucien Greathouse
98238e4516 Fix longstanding message_queue warning 2020-12-14 10:12:51 -08:00
Lucien Greathouse
23b8308282 Fix lockfile not being updated for 6.0.0-rc.3 2020-11-19 11:50:06 -08:00
Lucien Greathouse
a1f7cdc2b6 Release 6.0.0-rc.3 2020-11-19 11:47:07 -08:00
Lucien Greathouse
dcb30351c5 Fix null referent handling 2020-11-19 11:44:53 -08:00
Lucien Greathouse
1c0dc60071 Update CHANGELOG 2020-11-19 11:31:46 -08:00
Lucien Greathouse
0401c3ac0e Mark HttpEnabled as not scriptable as well 2020-11-19 11:20:30 -08:00
Lucien Greathouse
3b800d1cd7 Patch ReflectionDatabase to turn off SourceAssetId 2020-11-19 11:06:03 -08:00
Lucien Greathouse
35efb464e5 Fix incorrect tag in CHANGELOG 2020-11-19 10:33:09 -08:00
Lucien Greathouse
19a955a327 Release memofs 0.1.3 2020-11-19 10:26:37 -08:00
Lucien Greathouse
836b18e68a Release 6.0.0-rc.2 2020-11-19 10:25:25 -08:00
Lucien Greathouse
046dc0d598 plugin: Fix grammar in comments a bit :) 2020-11-16 11:49:30 -08:00
Lucien Greathouse
039d92ce78 plugin: Support ClassName changes in applyPatch 2020-11-16 11:44:56 -08:00
Lucien Greathouse
2136da15d6 plugin: Ensure InstanceMap deletes existing entries before inserting 2020-11-16 11:44:39 -08:00
Lucien Greathouse
e5041d80ef plugin: Fix warning in applyPatch.lua 2020-11-11 17:11:34 -08:00
Lucien Greathouse
f66860bdfe Break apart plugin reconciler (#332)
* Start splitting apart reconciler, with tests

* Reify children in reify

* Baseline hydrate implementation

* Remove debug print

* Scaffold out diff implementation, just supporting name changes

* invariant -> error in decodeValue

* Flesh out diff and add getProperty

* Clear out top-level reconciler interface, start updating code that touches it

* Address review feedback

* Add (experimental) Selene configuration

* Add emptiness checks to PatchSet, remove unimplement invert method

* Improve descendant destruction behavior in InstanceMap

* Track instanceId on all reify errors

* Base implementation of applyPatch, returning partial patches on failure

* Change reify to accept InstanceMap and insert instances into it

* Start testing applyPatch for removals

* Add test for applyPatch adding instances successfully and not

* Add , which is just error with formatting

* Correctly use new diff and applyPatch APIs

* Improve applyPatch logging and fix field name typo

* Better debug output when reify fails

* Print out unapplied patch in debug mode

* Don't write properties if their values are not different.

This was exposed trying to sync the Rojo plugin, which
has a gigantic ModuleScript in it with the reflection
database. This workaround was present in some form in
many versions of Rojo, and I guess we still need it.

This time, I actually documented why it's here so that
I don't forget for the umpteenth time...

* Add placeholder test that needs to happen still

* Introduce easier plugin testing, write applyPatch properties test

* Delete legacy get/setCanonicalProperty files

* Fix trying to remove numbers instead of instances

* Change applyPatch to return partial patches instead of binary success

* Work towards being able to decode and apply refs

* Add helpers for PatchSet assertions

* Apply refs in reify, test all cases

* Improve diagnostics when patches fail to apply

* Stop logging when destroying untracked instances, it's ok

* Remove read before setting property in applyPatch

* Fix diff thinking all properties are changed
2020-11-11 16:30:23 -08:00
Lucien Greathouse
50f0a2bd2e Update CLI dependencies 2020-10-29 10:36:20 -07:00
Lucien Greathouse
7cd9bd383e Update to latest reflection database 2020-10-29 10:35:55 -07:00
Lucien Greathouse
45a20a1633 Remove outdated notices 2020-09-09 17:39:24 -07:00
Lucien Greathouse
ec5b3f80ef Fix theme component error regression 2020-07-03 12:21:49 -07:00
Lucien Greathouse
3b257ea87a Update repo references after Roblox move 2020-06-23 11:55:46 -07:00
Lucien Greathouse
6b82cead9c Move from rojo-rbx org to Roblox org 2020-06-22 14:14:42 -07:00
cliffchapmanrbx
79ae4c52cd Enable CLA bot (#333) 2020-06-22 14:11:53 -07:00
Lucien Greathouse
a4616cda7d Fix test place's CharacterAutoLoads value 2020-06-20 22:16:35 -07:00
Lucien Greathouse
95648361be Recreate test place, just running in Studio 2020-06-20 21:51:26 -07:00
Lucien Greathouse
0c41e9c10b Depend on latest Rojo release from Rojo 2020-06-20 21:50:25 -07:00
Lucien Greathouse
61c7ef3cb0 plugin: lazily access settings() to help with testing 2020-06-20 21:50:14 -07:00
Lucien Greathouse
65898125d0 Update changelog 2020-06-17 23:14:24 -07:00
Lucien Greathouse
da05078ff3 Load project file from VFS instead of through fs.
Fixes #320.

Previously, the root project file was loaded via methods on Project
(which do not know about the VFS) instead of through the VFS like
all other disk access.

This meant that Rojo was unable to build its own plugin because
there is no project file on the real disk, only in the VFS.
2020-06-17 23:13:29 -07:00
Lucien Greathouse
badb5c3636 Stop redundantly adding ignore paths when starting ServeSession 2020-06-17 22:54:35 -07:00
Lucien Greathouse
9453588ab1 Load built-in plugin from absolute path to make errors more apparent 2020-06-17 22:54:13 -07:00
Lucien Greathouse
4cbb3874a4 Use anyhow error reporting instead of custom 2020-06-17 14:56:09 -07:00
Lucien Greathouse
940aff7ef4 Enable globIgnorePaths by default 2020-06-17 14:42:46 -07:00
Lucien Greathouse
a3edb93273 Update Changelog 2020-06-17 14:38:39 -07:00
Lucien Greathouse
782b054b1a Pass build watch argument into Vfs 2020-06-17 14:11:48 -07:00
Lucien Greathouse
fc27b2911e Allow turning off file watching in memofs.
Also preemptively bumping version to 0.1.3 so I don't forget on next release
2020-06-17 14:06:44 -07:00
Lucien Greathouse
486b067567 Flatten snapshot middleware to be much simpler (#324)
* First take at flattening middleware for simpler code and better perf

* Undo debug prints

* Fix using wrong path in snapshot_from_vfs

* Disable some broken tests

* Re-enable (mistakenly?) disabled CSV test

* Fix some tests

* Update project file tests

* Fix benchmark
2020-06-17 13:47:09 -07:00
Lucien Greathouse
bdd1afea57 Run CI on master and PRs to master only 2020-05-20 15:30:44 -07:00
Lucien Greathouse
5ccd02939b Replace rojo-test with regular tests folder again (#323)
* Replace rojo-test with regular tests folder again

* Bump MSRV to 1.43.1
2020-05-20 15:30:05 -07:00
Lucien Greathouse
ca5b8ab309 Restore improperly tested dependency on rojo from rojo-test 2020-05-20 11:56:34 -07:00
Lucien Greathouse
9481fdd38d Add missing Cargo.lock change 2020-05-02 21:47:36 -07:00
Lucien Greathouse
56bf6d282b Stop building Rojo in rojo-test, since it doesn't work as intended 2020-05-02 21:39:13 -07:00
Lucien Greathouse
5364c9c1bc Fix Lua string escaping.
Closes #314.
2020-04-16 12:00:02 -07:00
Lucien Greathouse
a4d4beeb97 Update default place template 2020-03-30 11:34:41 -07:00
Lucien Greathouse
30a01381be Fix malformed CSV files causing crashes; fixes #310 2020-03-30 11:12:20 -07:00
Lucien Greathouse
def99a9e4d Update release job to fix 6.0.0 release 2020-03-29 17:09:52 -07:00
Lucien Greathouse
1214fc8b0d Release 6.0.0-rc.1
This change also includes some minor packaging changes in order to make Cargo happy.
2020-03-29 16:58:37 -07:00
Lucien Greathouse
5a5b1268d3 Update changelog 2020-03-29 16:03:58 -07:00
jeparlefrancais
6a1fffd1ce Infer class name (#210)
* infer service names

* Update project code and add support for StarterPlayer

* Store parent_class in InstigatingSource

* Update snapshots

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2020-03-29 16:03:15 -07:00
Lucien Greathouse
571ef3060a Update Changelog 2020-03-29 13:46:27 -07:00
jeparlefrancais
3cf82e112f Install plugin from CLI (#304)
* add install command

* cargo fmt

* filter spec files

* Update src/cli/plugin.rs

Co-Authored-By: Lucien Greathouse <me@lpghatguy.com>

* Update src/cli/plugin.rs

Co-Authored-By: Lucien Greathouse <me@lpghatguy.com>

* fix comments

* encode plugin with rbx_binary

* update build script

* refactor pathbuf error into io error

* fix rojo typo

* remove snafu

* Update `snapshot_from_fs_path`

* Print `rerun-if-changed` even for directories, in order to run the
  build.rs script when files are added.

* Switch `filter_map` loop to a regular for loop. I like the FP-style
  iterator stuff in Rust, but I think Result handling is easier in a
  normal loop. Also, I don't believe the result of read_dir implements
  `ExactSizedIterator`, so some of the wins of map+collect aren't there.

* Replace Result::unwrap with ? in build.rs

* Simplify error handling code in runtime

* Checkout with submodules

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2020-03-29 13:41:54 -07:00
Lucien Greathouse
9b459c20d6 Fix GHA only running on pushes to master 2020-03-29 13:17:06 -07:00
Lucien Greathouse
5c85cd27c3 Fix default place project.
Closes #311.
2020-03-29 12:21:55 -07:00
Lucien Greathouse
4bf73c7a8a Implement support for turning .json files into Lua modules (#308)
* Stub implementation

* Flesh out feature and add tests. Other snapshots currently failing.

* Blacklist .meta.json in JSON handler

* Write to correct property (Source) instead of Value

* Update changelog
2020-03-28 00:36:01 -07:00
Lucien Greathouse
62e51b7535 Upgrade to latest rbx-dom 2020-03-27 23:58:31 -07:00
Lucien Greathouse
729a7f0053 Turn panics into errors in ServeSession 2020-03-26 12:16:55 -07:00
Lucien Greathouse
03c297190d Make ServeSession::new fallible 2020-03-26 12:07:44 -07:00
Lucien Greathouse
9c790eddd7 Tidy up ServeSession now that trait bounds are gone 2020-03-26 12:06:16 -07:00
Lucien Greathouse
8ebe7e332b Update Changelog 2020-03-25 17:02:32 -07:00
Lucien Greathouse
f43777e37e Require a Rojo project again (#307) 2020-03-25 17:01:28 -07:00
Lucien Greathouse
691a8fcdeb Upgrade lockfile using latest rustc 2020-03-25 16:15:21 -07:00
Lucien Greathouse
69c0e8d70e Fix warnings 2020-03-21 17:49:56 -07:00
Lucien Greathouse
330c92c9a8 Refactor ChangeProcessor loop to get rid of panics 2020-03-21 17:47:25 -07:00
Lucien Greathouse
cf0ff60d31 plugin: Add simple signal implementation for future work 2020-03-18 23:31:22 -07:00
Lucien Greathouse
9e9cf5dd1f plugin: Add support for pausing updates tracked by InstanceMap 2020-03-18 23:27:30 -07:00
Lucien Greathouse
5768d8e4a4 plugin: Miscellaneous cleanup 2020-03-18 23:15:03 -07:00
Lucien Greathouse
3b433e53be Memofs v0.1.1 2020-03-18 18:35:44 -07:00
Lucien Greathouse
28ddf40344 memofs: Update fs-err and use it more 2020-03-18 18:06:58 -07:00
Lucien Greathouse
c1286db9c1 Update Changelog 2020-03-18 16:26:41 -07:00
Lucien Greathouse
f13940262e Update CHANGELOG 2020-03-18 12:03:50 -07:00
Lucien Greathouse
9f0a6101b8 Add configurable color options 2020-03-18 12:03:07 -07:00
Lucien Greathouse
0b0fe01a7c Tidy up root repository files 2020-03-18 11:40:12 -07:00
Lucien Greathouse
85e098d5c8 Update README 2020-03-18 11:36:50 -07:00
Lucien Greathouse
e8d1faf4e2 Update changelog 2020-03-18 10:43:42 -07:00
Lucien Greathouse
2a46df1110 Expose two-way sync.
- Convert plugin DevSettings flag to settings panel feature
- Remove server feature, always enable write API
2020-03-18 10:39:40 -07:00
Lucien Greathouse
1601e6d26e Update changelog 2020-03-17 23:20:40 -07:00
Lucien Greathouse
0e4f6dea2b plugin: Add setting for opening scripts externally 2020-03-17 23:20:05 -07:00
Lucien Greathouse
a2356773dc Add checkbox and fill out settings panel 2020-03-17 23:14:32 -07:00
Lucien Greathouse
4a4da4737d Fix plugin settings persistent 2020-03-17 23:03:59 -07:00
Lucien Greathouse
2cefd1bf2e plugin: Add PluginSettings context item, render it in settings screen 2020-03-17 23:03:01 -07:00
Lucien Greathouse
c5ce15fe34 plugin: Add dummy settings panel 2020-03-17 22:38:53 -07:00
Lucien Greathouse
76dea568c9 Update changelog 2020-03-17 22:30:00 -07:00
Lucien Greathouse
8e81140eff Increase verbosity of logging 2020-03-17 22:29:23 -07:00
Lucien Greathouse
d58e1f0792 Add logging when running rojo build 2020-03-17 22:28:38 -07:00
Lucien Greathouse
830c242751 plugin: Stop using codename in dev mode 2020-03-17 22:25:04 -07:00
Lucien Greathouse
91d45afd0f Add plugin feature 'UnstableOpenScriptsExternally' 2020-03-17 18:13:52 -07:00
Lucien Greathouse
102c77b23e Implement /api/open/{id} to open a script by ID in your default editor 2020-03-17 17:50:54 -07:00
Lucien Greathouse
aa4039a2e7 bye snafu 2020-03-16 23:37:00 -07:00
Lucien Greathouse
c065ded440 Tidy up SnapshotError a lot 2020-03-16 21:35:46 -07:00
Lucien Greathouse
f69096dadb Use thiserror and anyhow for command-level error types 2020-03-16 21:13:38 -07:00
Lucien Greathouse
363f95ba14 memofs: Use fs_err instead of std::fs when possible 2020-03-16 20:35:58 -07:00
Lucien Greathouse
dcc15e8911 Refactor upload to use ServeSession and drop common_setup 2020-03-16 20:20:12 -07:00
288 changed files with 29027 additions and 4952 deletions

View File

@@ -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.43.1"]
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'

View File

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

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

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

View File

@@ -33,7 +33,7 @@ stds.plugin = {
stds.testez = {
read_globals = {
"describe",
"it", "itFOCUS", "itSKIP",
"it", "itFOCUS", "itSKIP", "itFIXME",
"FOCUS", "SKIP", "HACK_NO_XPCALL",
"expect",
}

View File

@@ -1,6 +1,50 @@
# Rojo Changelog
## Unreleased Changes for 0.6.x
## Unreleased Changes
## [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))
@@ -108,7 +152,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.
@@ -305,4 +349,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)

View File

@@ -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.
@@ -33,15 +32,13 @@ Please file issues and we'll try to help figure out what the best way forward is
## 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
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`
7. Publish the CLI
* `cargo publish`
8. Build and upload the plugin
@@ -52,4 +49,5 @@ The Rojo release process is pretty manual right now. If you need to do it, here'
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

2576
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "rojo"
version = "0.6.0-alpha.3"
version = "6.0.2"
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
description = "Enables professional-grade development tools for Roblox developers"
license = "MPL-2.0"
@@ -11,7 +11,6 @@ readme = "README.md"
edition = "2018"
exclude = [
"/plugin/**",
"/test-projects/**",
]
@@ -24,26 +23,11 @@ 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",
]
@@ -61,12 +45,15 @@ name = "build"
harness = false
[dependencies]
memofs = { version = "0.1.0", path = "memofs" }
memofs = { version = "0.1.2", path = "memofs" }
anyhow = "1.0.27"
backtrace = "0.3"
bincode = "1.2.1"
crossbeam-channel = "0.4.0"
csv = "1.1.1"
env_logger = "0.7.1"
fs-err = "2.2.0"
futures = "0.1.29"
globset = "0.4.4"
humantime = "1.3.0"
@@ -85,22 +72,31 @@ regex = "1.3.1"
reqwest = "0.9.20"
ritz = "0.1.0"
rlua = "0.17.0"
roblox_install = "0.2.2"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0"
snafu = "0.6.0"
structopt = "0.3.5"
termcolor = "1.0.5"
thiserror = "1.0.11"
tokio = "0.1.22"
uuid = { version = "0.8.1", features = ["v4", "serde"] }
[target.'cfg(windows)'.dependencies]
winreg = "0.6.2"
[build-dependencies]
memofs = { version = "0.1.3", path = "memofs" }
anyhow = "1.0.27"
bincode = "1.2.1"
fs-err = "2.3.0"
maplit = "1.0.1"
[dev-dependencies]
rojo-insta-ext = { path = "rojo-insta-ext" }
criterion = "0.3"
insta = { version = "0.13.1", features = ["redactions"] }
insta = { version = "1.3.0", features = ["redactions"] }
lazy_static = "1.2"
paste = "0.1"
pretty_assertions = "0.6.1"

View File

@@ -13,8 +13,8 @@
<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 href="https://rojo.space/docs">
<img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" />
</a>
</div>
@@ -34,11 +34,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 +48,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.43.1 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.

View File

@@ -4,27 +4,19 @@
"$className": "DataModel",
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"Common": {
"$path": "src/common"
"$path": "src/shared"
}
},
"ServerScriptService": {
"$className": "ServerScriptService",
"Server": {
"$path": "src/server"
}
},
"StarterPlayer": {
"$className": "StarterPlayer",
"StarterPlayerScripts": {
"$className": "StarterPlayerScripts",
"Client": {
"$path": "src/client"
}
@@ -32,7 +24,6 @@
},
"Workspace": {
"$className": "Workspace",
"$properties": {
"FilteringEnabled": true
},
@@ -60,7 +51,6 @@
}
},
"Lighting": {
"$className": "Lighting",
"$properties": {
"Ambient": [
0,
@@ -74,16 +64,9 @@
}
},
"SoundService": {
"$className": "SoundService",
"$properties": {
"RespectFilteringEnabled": true
}
},
"HttpService": {
"$className": "HttpService",
"$properties": {
"HttpEnabled": true
}
}
}
}

BIN
assets/images/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

5
assets/images/back.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/images/circle-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

BIN
assets/images/circle-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
assets/images/circle-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

BIN
assets/images/close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

15
assets/images/close.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

View 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

View File

@@ -35,6 +35,7 @@ fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
let options = BuildCommand {
project: input,
watch: false,
output,
};

77
build.rs Normal file
View File

@@ -0,0 +1,77 @@
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)?;
Ok(())
}

View File

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

View File

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

View File

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

View File

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

@@ -0,0 +1,3 @@
[tools]
rojo = { source = "rojo-rbx/rojo", version = "6.0.0-rc.3" }
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }

View File

@@ -2,5 +2,14 @@
## Unreleased Changes
## 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

View File

@@ -1,7 +1,7 @@
[package]
name = "memofs"
description = "Virtual filesystem with configurable backends."
version = "0.1.0"
version = "0.1.3"
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]
notify = "4.0.15"
crossbeam-channel = "0.4.0"
fs-err = "2.3.0"
notify = "4.0.15"
serde = { version = "1.0", features = ["derive"] }

View File

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

View File

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

View File

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

View 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(),

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

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
stds.roblox = {
read_globals = {
game = {
other_fields = true,
},
-- Roblox globals
"script",
-- Extra functions
"tick", "warn",
"wait", "typeof",
-- Types
"CFrame",
"Color3",
"Enum",
"Instance",
"NumberRange",
"Rect",
"UDim", "UDim2",
"Vector2", "Vector3",
"Vector2int16", "Vector3int16",
}
}
stds.testez = {
read_globals = {
"describe",
"it", "itFOCUS", "itSKIP",
"FOCUS", "SKIP", "HACK_NO_XPCALL",
"expect",
}
}
ignore = {
"212", -- unused arguments
}
std = "lua51+roblox"
files["**/*.spec.lua"] = {
std = "+testez",
}

View File

@@ -0,0 +1,2 @@
# rbx_dom_lua
Roblox Lua implementation of rbx-dom mechanisms, intended to work with rbx_dom_weak and friends.

View File

@@ -0,0 +1,6 @@
{
"name": "rbx_dom_lua",
"tree": {
"$path": "src"
}
}

View File

@@ -0,0 +1,242 @@
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 encoders
encoders = {
Bool = identity,
Content = identity,
Float32 = serializeFloat,
Float64 = serializeFloat,
Int32 = identity,
Int64 = identity,
String = identity,
BinaryString = base64.encode,
SharedString = base64.encode,
BrickColor = function(value)
return value.Number
end,
CFrame = function(value)
return {value:GetComponents()}
end,
Color3 = function(value)
return {value.r, value.g, value.b}
end,
NumberRange = function(value)
return {value.Min, value.Max}
end,
NumberSequence = function(value)
local keypoints = {}
for index, keypoint in ipairs(value.Keypoints) do
keypoints[index] = {
Time = keypoint.Time,
Value = keypoint.Value,
Envelope = keypoint.Envelope,
}
end
return {
Keypoints = keypoints,
}
end,
ColorSequence = function(value)
local keypoints = {}
for index, keypoint in ipairs(value.Keypoints) do
keypoints[index] = {
Time = keypoint.Time,
Color = encoders.Color3(keypoint.Value),
}
end
return {
Keypoints = keypoints,
}
end,
Rect = function(value)
return {
Min = {value.Min.X, value.Min.Y},
Max = {value.Max.X, value.Max.Y},
}
end,
UDim = function(value)
return {value.Scale, value.Offset}
end,
UDim2 = function(value)
return {value.X.Scale, value.X.Offset, value.Y.Scale, value.Y.Offset}
end,
Vector2 = function(value)
return {
serializeFloat(value.X),
serializeFloat(value.Y),
}
end,
Vector2int16 = function(value)
return {value.X, value.Y}
end,
Vector3 = function(value)
return {
serializeFloat(value.X),
serializeFloat(value.Y),
serializeFloat(value.Z),
}
end,
Vector3int16 = function(value)
return {value.X, value.Y, value.Z}
end,
PhysicalProperties = function(value)
if value == nil then
return nil
else
return {
Density = value.Density,
Friction = value.Friction,
Elasticity = value.Elasticity,
FrictionWeight = value.FrictionWeight,
ElasticityWeight = value.ElasticityWeight,
}
end
end,
Ref = function(value)
return nil
end,
}
local decoders = {
Bool = identity,
Content = identity,
Enum = identity,
Float32 = identity,
Float64 = identity,
Int32 = identity,
Int64 = identity,
String = identity,
BinaryString = base64.decode,
SharedString = base64.decode,
BrickColor = BrickColor.new,
CFrame = unpackDecoder(CFrame.new),
Color3 = unpackDecoder(Color3.new),
Color3uint8 = unpackDecoder(Color3.fromRGB),
NumberRange = unpackDecoder(NumberRange.new),
UDim = unpackDecoder(UDim.new),
UDim2 = unpackDecoder(UDim2.new),
Vector2 = unpackDecoder(Vector2.new),
Vector2int16 = unpackDecoder(Vector2int16.new),
Vector3 = unpackDecoder(Vector3.new),
Vector3int16 = unpackDecoder(Vector3int16.new),
Rect = function(value)
return Rect.new(value.Min[1], value.Min[2], value.Max[1], value.Max[2])
end,
NumberSequence = function(value)
local keypoints = {}
for index, keypoint in ipairs(value.Keypoints) do
keypoints[index] = NumberSequenceKeypoint.new(
keypoint.Time,
keypoint.Value,
keypoint.Envelope
)
end
return NumberSequence.new(keypoints)
end,
ColorSequence = function(value)
local keypoints = {}
for index, keypoint in ipairs(value.Keypoints) do
keypoints[index] = ColorSequenceKeypoint.new(
keypoint.Time,
Color3.new(unpack(keypoint.Color))
)
end
return ColorSequence.new(keypoints)
end,
PhysicalProperties = function(properties)
if properties == nil then
return nil
else
return PhysicalProperties.new(
properties.Density,
properties.Friction,
properties.Elasticity,
properties.FrictionWeight,
properties.ElasticityWeight
)
end
end,
Ref = function()
return nil
end,
}
local EncodedValue = {}
function EncodedValue.decode(encodedValue)
local decoder = decoders[encodedValue.Type]
if decoder ~= nil then
return true, decoder(encodedValue.Value)
end
return false, "Couldn't decode value " .. tostring(encodedValue.Type)
end
function EncodedValue.encode(rbxValue, propertyType)
assert(propertyType ~= nil, "Property type descriptor is required")
if propertyType.type == "Data" then
local encoder = encoders[propertyType.name]
if encoder == nil then
return false, ("Missing encoder for property type %q"):format(propertyType.name)
end
if encoder ~= nil then
return true, {
Type = propertyType.name,
Value = encoder(rbxValue),
}
end
elseif propertyType.type == "Enum" then
return true, {
Type = "Enum",
Value = rbxValue.Value,
}
end
return false, ("Unknown property descriptor type %q"):format(tostring(propertyType.type))
end
return EncodedValue

View File

@@ -0,0 +1,127 @@
return function()
local RbxDom = require(script.Parent)
local EncodedValue = require(script.Parent.EncodedValue)
it("should decode Rect values", function()
local input = {
Type = "Rect",
Value = {
Min = {1, 2},
Max = {3, 4},
},
}
local output = Rect.new(1, 2, 3, 4)
local ok, decoded = EncodedValue.decode(input)
assert(ok, decoded)
expect(decoded).to.equal(output)
end)
it("should decode ColorSequence values", function()
local input = {
Type = "ColorSequence",
Value = {
Keypoints = {
{
Time = 0,
Color = { 0.12, 0.34, 0.56 },
},
{
Time = 1,
Color = { 0.13, 0.33, 0.37 },
},
}
},
}
local output = ColorSequence.new({
ColorSequenceKeypoint.new(0, Color3.new(0.12, 0.34, 0.56)),
ColorSequenceKeypoint.new(1, Color3.new(0.13, 0.33, 0.37)),
})
local ok, decoded = EncodedValue.decode(input)
assert(ok, decoded)
expect(decoded).to.equal(output)
end)
it("should decode NumberSequence values", function()
local input = {
Type = "NumberSequence",
Value = {
Keypoints = {
{
Time = 0,
Value = 0.5,
Envelope = 0,
},
{
Time = 1,
Value = 0.5,
Envelope = 0,
},
}
},
}
local output = NumberSequence.new({
NumberSequenceKeypoint.new(0, 0.5, 0),
NumberSequenceKeypoint.new(1, 0.5, 0),
})
local ok, decoded = EncodedValue.decode(input)
assert(ok, decoded)
expect(decoded).to.equal(output)
end)
it("should decode PhysicalProperties values", function()
local input = {
Type = "PhysicalProperties",
Value = {
Density = 0.1,
Friction = 0.2,
Elasticity = 0.3,
FrictionWeight = 0.4,
ElasticityWeight = 0.5,
},
}
local output = PhysicalProperties.new(
0.1,
0.2,
0.3,
0.4,
0.5
)
local ok, decoded = EncodedValue.decode(input)
assert(ok, decoded)
expect(decoded).to.equal(output)
end)
-- This part of rbx_dom_lua needs some work still.
itSKIP("should encode Rect values", function()
local input = Rect.new(10, 20, 30, 40)
local output = {
Type = "Rect",
Value = {
Min = {10, 20},
Max = {30, 40},
},
}
local descriptor = RbxDom.findCanonicalPropertyDescriptor("ImageLabel", "SliceCenter")
local ok, encoded = EncodedValue.encode(input, descriptor)
assert(ok, encoded)
expect(encoded.Type).to.equal(output.Type)
expect(encoded.Value.Min[1]).to.equal(output.Value.Min[1])
expect(encoded.Value.Min[2]).to.equal(output.Value.Min[2])
expect(encoded.Value.Max[1]).to.equal(output.Value.Max[1])
expect(encoded.Value.Max[2]).to.equal(output.Value.Max[2])
end)
end

View 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

View File

@@ -0,0 +1,80 @@
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)
return setmetatable({
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
return {
classes = require(script.classes)
}

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

View 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

View File

@@ -0,0 +1,47 @@
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, key)
local tagList = CollectionService:GetTags(instance)
return true, table.concat(tagList, "\0")
end,
write = function(instance, key, value)
local existingTags = CollectionService:GetTags(instance)
local unseenTags = {}
for _, tag in ipairs(existingTags) do
unseenTags[tag] = true
end
local tagList = string.split(value, "\0")
for _, tag in ipairs(tagList) 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,
},
},
}

View File

@@ -0,0 +1,67 @@
local ReflectionDatabase = require(script.ReflectionDatabase)
local Error = require(script.Error)
local PropertyDescriptor = require(script.PropertyDescriptor)
local function findCanonicalPropertyDescriptor(className, propertyName)
local currentClassName = className
repeat
local currentClass = ReflectionDatabase.classes[currentClassName]
if currentClass == nil then
return currentClass
end
local propertyData = currentClass.properties[propertyName]
if propertyData ~= nil then
if propertyData.isCanonical then
return PropertyDescriptor.fromRaw(propertyData, currentClassName, propertyName)
end
if propertyData.canonicalName ~= nil then
return PropertyDescriptor.fromRaw(
currentClass.properties[propertyData.canonicalName],
currentClassName,
propertyData.canonicalName)
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),
}

View File

@@ -0,0 +1,7 @@
return function()
local RbxDom = require(script.Parent)
it("should load", function()
expect(RbxDom).to.be.ok()
end)
end

View File

@@ -0,0 +1,35 @@
{
"name": "rbx_dom_lua test place",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"RbxDom": {
"$path": "src"
},
"TestEZ": {
"$path": "modules/testez/lib"
}
},
"ServerScriptService": {
"$className": "ServerScriptService",
"Run Tests": {
"$path": "test.server.lua"
}
},
"Players": {
"$className": "Players",
"$properties": {
"CharacterAutoLoads": false
}
},
"HttpService": {
"$className": "HttpService",
"$properties": {
"HttpEnabled": true
}
}
}
}

View File

@@ -0,0 +1,7 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LIB_ROOT = ReplicatedStorage.RbxDom
local TestEZ = require(ReplicatedStorage.TestEZ)
TestEZ.TestBootstrap:run({LIB_ROOT})

View File

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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,7 @@
local Rojo = script:FindFirstAncestor("Rojo")
local Roact = require(Rojo.Roact)
local StudioPluginContext = Roact.createContext(nil)
return StudioPluginContext

View 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

View 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

View 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

View File

@@ -0,0 +1,7 @@
local Rojo = script:FindFirstAncestor("Rojo")
local Roact = require(Rojo.Roact)
local StudioToolbarContext = Roact.createContext(nil)
return StudioToolbarContext

View 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

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

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

View 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

View 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

View 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

View File

@@ -0,0 +1,154 @@
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 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 = "",
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 = "",
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
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

View 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 = "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

View File

@@ -0,0 +1,7 @@
return {
NotConnected = require(script.NotConnected),
Settings = require(script.Settings),
Connecting = require(script.Connecting),
Connected = require(script.Connected),
Error = require(script.Error),
}

239
plugin/src/App/Theme.lua Normal file
View File

@@ -0,0 +1,239 @@
--[[
Theming system taking advantage of Roact's new context API.
Doesn't use colors provided by Studio and instead just branches on theme
name. This isn't exactly best practice.
]]
-- Studio does not exist outside Roblox Studio, so we'll lazily initialize it
-- when possible.
local _Studio
local function getStudio()
if _Studio == nil then
_Studio = settings():GetService("Studio")
end
return _Studio
end
local Rojo = script:FindFirstAncestor("Rojo")
local Roact = require(Rojo.Roact)
local Log = require(Rojo.Log)
local strict = require(script.Parent.Parent.strict)
-- Copying hex colors back and forth from design programs is faster
local function hexColor(decimal)
local red = bit32.band(bit32.rshift(decimal, 16), 2^8 - 1)
local green = bit32.band(bit32.rshift(decimal, 8), 2^8 - 1)
local blue = bit32.band(decimal, 2^8 - 1)
return Color3.fromRGB(red, green, blue)
end
local BRAND_COLOR = hexColor(0xE13835)
local lightTheme = strict("LightTheme", {
BackgroundColor = hexColor(0xF0F0F0),
Button = {
Solid = {
ActionFillColor = hexColor(0xFFFFFF),
ActionFillTransparency = 0.8,
Enabled = {
TextColor = hexColor(0xFFFFFF),
BackgroundColor = BRAND_COLOR,
},
Disabled = {
TextColor = hexColor(0xFFFFFF),
BackgroundColor = BRAND_COLOR,
},
},
Bordered = {
ActionFillColor = hexColor(0x000000),
ActionFillTransparency = 0.9,
Enabled = {
TextColor = hexColor(0x393939),
BorderColor = hexColor(0xACACAC),
},
Disabled = {
TextColor = hexColor(0x393939),
BorderColor = hexColor(0xACACAC),
},
},
},
Checkbox = {
Active = {
IconColor = hexColor(0xFFFFFF),
BackgroundColor = BRAND_COLOR,
},
Inactive = {
IconColor = hexColor(0xCACACA),
BorderColor = hexColor(0xAFAFAF),
},
},
AddressEntry = {
TextColor = hexColor(0x000000),
PlaceholderColor = hexColor(0x8C8C8C)
},
BorderedContainer = {
BorderColor = hexColor(0xCBCBCB),
BackgroundColor = hexColor(0xE0E0E0),
},
Spinner = {
ForegroundColor = BRAND_COLOR,
BackgroundColor = hexColor(0xE0E0E0),
},
ConnectionDetails = {
ProjectNameColor = hexColor(0x00000),
AddressColor = hexColor(0x00000),
DisconnectColor = BRAND_COLOR,
},
Settings = {
DividerColor = hexColor(0xCBCBCB),
Navbar = {
BackButtonColor = hexColor(0x000000),
TextColor = hexColor(0x000000),
},
Setting = {
NameColor = hexColor(0x000000),
DescriptionColor = hexColor(0x5F5F5F),
},
},
Header = {
LogoColor = BRAND_COLOR,
VersionColor = hexColor(0x727272),
},
ErrorColor = hexColor(0x000000),
ScrollBarColor = hexColor(0x000000),
})
local darkTheme = strict("DarkTheme", {
BackgroundColor = hexColor(0x272727),
Button = {
Solid = {
ActionFillColor = hexColor(0xFFFFFF),
ActionFillTransparency = 0.8,
Enabled = {
TextColor = hexColor(0xFFFFFF),
BackgroundColor = BRAND_COLOR,
},
Disabled = {
TextColor = hexColor(0xFFFFFF),
BackgroundColor = BRAND_COLOR,
},
},
Bordered = {
ActionFillColor = hexColor(0xFFFFFF),
ActionFillTransparency = 0.9,
Enabled = {
TextColor = hexColor(0xDBDBDB),
BorderColor = hexColor(0x535353),
},
Disabled = {
TextColor = hexColor(0xDBDBDB),
BorderColor = hexColor(0x535353),
},
},
},
Checkbox = {
Active = {
IconColor = hexColor(0xFFFFFF),
BackgroundColor = BRAND_COLOR,
},
Inactive = {
IconColor = hexColor(0x484848),
BorderColor = hexColor(0x5A5A5A),
},
},
AddressEntry = {
TextColor = hexColor(0xFFFFFF),
PlaceholderColor = hexColor(0x717171)
},
BorderedContainer = {
BorderColor = hexColor(0x535353),
BackgroundColor = hexColor(0x323232),
},
Spinner = {
ForegroundColor = BRAND_COLOR,
BackgroundColor = hexColor(0x323232),
},
ConnectionDetails = {
ProjectNameColor = hexColor(0xFFFFFF),
AddressColor = hexColor(0xFFFFFF),
DisconnectColor = hexColor(0xFFFFFF),
},
Settings = {
DividerColor = hexColor(0x535353),
Navbar = {
BackButtonColor = hexColor(0xFFFFFF),
TextColor = hexColor(0xFFFFFF),
},
Setting = {
NameColor = hexColor(0xFFFFFF),
DescriptionColor = hexColor(0xD3D3D3),
},
},
Header = {
LogoColor = BRAND_COLOR,
VersionColor = hexColor(0xD3D3D3)
},
ErrorColor = hexColor(0xFFFFFF),
ScrollBarColor = hexColor(0xFFFFFF),
})
local Context = Roact.createContext(lightTheme)
local StudioProvider = Roact.Component:extend("StudioProvider")
-- Pull the current theme from Roblox Studio and update state with it.
function StudioProvider:updateTheme()
local studioTheme = getStudio().Theme
if studioTheme.Name == "Light" then
self:setState({
theme = lightTheme,
})
elseif studioTheme.Name == "Dark" then
self:setState({
theme = darkTheme,
})
else
Log.warn("Unexpected theme '{}'' -- falling back to light theme!", studioTheme.Name)
self:setState({
theme = lightTheme,
})
end
end
function StudioProvider:init()
self:updateTheme()
end
function StudioProvider:render()
return Roact.createElement(Context.Provider, {
value = self.state.theme,
}, self.props[Roact.Children])
end
function StudioProvider:didMount()
self.connection = getStudio().ThemeChanged:Connect(function()
self:updateTheme()
end)
end
function StudioProvider:willUnmount()
self.connection:Disconnect()
end
local function with(callback)
return Roact.createElement(Context.Consumer, {
render = callback,
})
end
return {
StudioProvider = StudioProvider,
Consumer = Context.Consumer,
with = with,
}

View File

@@ -0,0 +1,58 @@
local Rojo = script:FindFirstAncestor("Rojo")
local Roact = require(Rojo.Roact)
local Log = require(Rojo.Log)
local LERP_DATA_TYPES = {
Color3 = true,
UDim = true,
UDim2 = true,
Vector2 = true,
Vector3 = true,
}
local function fromMotor(motor)
local motorBinding, setMotorBinding = Roact.createBinding(motor:getValue())
motor:onStep(setMotorBinding)
return motorBinding
end
local function mapLerp(binding, value1, value2)
local valueType = typeof(value1)
if valueType ~= typeof(value2) then
Log.error("Type mismatch between values ({}, {}})", valueType, typeof(value2))
end
return binding:map(function(position)
if valueType == "number" then
return value1 - (value2 - value1) * position
elseif LERP_DATA_TYPES[valueType] then
return value1:lerp(value2, position)
else
Log.error("Unable to interpolate type {}", valueType)
end
end)
end
local function deriveProperty(binding, propertyName)
return binding:map(function(values)
return values[propertyName]
end)
end
local function blendAlpha(alphaValues)
local alpha = 0
for _, value in pairs(alphaValues) do
alpha = alpha + (1 - alpha) * value
end
return alpha
end
return {
fromMotor = fromMotor,
mapLerp = mapLerp,
deriveProperty = deriveProperty,
blendAlpha = blendAlpha,
}

226
plugin/src/App/init.lua Normal file
View File

@@ -0,0 +1,226 @@
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Roact = require(Rojo.Roact)
local Log = require(Rojo.Log)
local Assets = require(Plugin.Assets)
local Version = require(Plugin.Version)
local Config = require(Plugin.Config)
local strict = require(Plugin.strict)
local Dictionary = require(Plugin.Dictionary)
local ServeSession = require(Plugin.ServeSession)
local ApiContext = require(Plugin.ApiContext)
local preloadAssets = require(Plugin.preloadAssets)
local Theme = require(script.Theme)
local PluginSettings = require(script.PluginSettings)
local Page = require(script.Page)
local StudioToolbar = require(script.Components.Studio.StudioToolbar)
local StudioToggleButton = require(script.Components.Studio.StudioToggleButton)
local StudioPluginGui = require(script.Components.Studio.StudioPluginGui)
local StudioPluginContext = require(script.Components.Studio.StudioPluginContext)
local StatusPages = require(script.StatusPages)
local AppStatus = strict("AppStatus", {
NotConnected = "NotConnected",
Settings = "Settings",
Connecting = "Connecting",
Connected = "Connected",
Error = "Error",
})
local e = Roact.createElement
local App = Roact.Component:extend("App")
function App:init()
preloadAssets()
self:setState({
appStatus = AppStatus.NotConnected,
guiEnabled = false,
})
end
function App:startSession(host, port, sessionOptions)
local baseUrl = ("http://%s:%s"):format(host, port)
local apiContext = ApiContext.new(baseUrl)
local serveSession = ServeSession.new({
apiContext = apiContext,
openScriptsExternally = sessionOptions.openScriptsExternally,
twoWaySync = sessionOptions.twoWaySync,
})
serveSession:onStatusChanged(function(status, details)
if status == ServeSession.Status.Connecting then
self:setState({
appStatus = AppStatus.Connecting,
})
elseif status == ServeSession.Status.Connected then
local address = ("%s:%s"):format(host, port)
self:setState({
appStatus = AppStatus.Connected,
projectName = details,
address = address,
})
elseif status == ServeSession.Status.Disconnected then
self.serveSession = nil
-- Details being present indicates that this
-- disconnection was from an error.
if details ~= nil then
Log.warn("Disconnected from an error: {}", details)
self:setState({
appStatus = AppStatus.Error,
errorMessage = tostring(details),
})
else
self:setState({
appStatus = AppStatus.NotConnected,
})
end
end
end)
serveSession:start()
self.serveSession = serveSession
end
function App:render()
local pluginName = "Rojo " .. Version.display(Config.version)
local function createPageElement(appStatus, additionalProps)
additionalProps = additionalProps or {}
local props = Dictionary.merge(additionalProps, {
component = StatusPages[appStatus],
active = self.state.appStatus == appStatus,
})
return e(Page, props)
end
return e(StudioPluginContext.Provider, {
value = self.props.plugin,
}, {
e(Theme.StudioProvider, nil, {
e(PluginSettings.StudioProvider, {
plugin = self.props.plugin,
}, {
gui = e(StudioPluginGui, {
id = pluginName,
title = pluginName,
active = self.state.guiEnabled,
initDockState = Enum.InitialDockState.Right,
initEnabled = false,
overridePreviousState = false,
floatingSize = Vector2.new(300, 200),
minimumSize = Vector2.new(300, 200),
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
onInitialState = function(initialState)
self:setState({
guiEnabled = initialState,
})
end,
onClose = function()
self:setState({
guiEnabled = false,
})
end,
}, {
NotConnectedPage = PluginSettings.with(function(settings)
return createPageElement(AppStatus.NotConnected, {
onConnect = function(host, port)
self:startSession(host, port, {
openScriptsExternally = settings:get("openScriptsExternally"),
twoWaySync = settings:get("twoWaySync"),
})
end,
onNavigateSettings = function()
self:setState({
appStatus = AppStatus.Settings,
})
end,
})
end),
Connecting = createPageElement(AppStatus.Connecting),
Connected = createPageElement(AppStatus.Connected, {
projectName = self.state.projectName,
address = self.state.address,
onDisconnect = function()
Log.trace("Disconnecting session")
self.serveSession:stop()
self.serveSession = nil
self:setState({
appStatus = AppStatus.NotConnected,
})
Log.trace("Session terminated by user")
end,
}),
Settings = createPageElement(AppStatus.Settings, {
onBack = function()
self:setState({
appStatus = AppStatus.NotConnected,
})
end,
}),
Error = createPageElement(AppStatus.Error, {
errorMessage = self.state.errorMessage,
onClose = function()
self:setState({
appStatus = AppStatus.NotConnected,
})
end,
}),
Background = Theme.with(function(theme)
return e("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundColor3 = theme.BackgroundColor,
ZIndex = 0,
BorderSizePixel = 0,
})
end),
}),
toolbar = e(StudioToolbar, {
name = pluginName,
}, {
button = e(StudioToggleButton, {
name = "Rojo",
tooltip = "Show or hide the Rojo panel",
icon = Assets.Images.PluginButton,
active = self.state.guiEnabled,
enabled = true,
onClick = function()
self:setState(function(state)
return {
guiEnabled = not state.guiEnabled,
}
end)
end,
})
}),
}),
}),
})
end
return App

View File

@@ -3,16 +3,45 @@ local strict = require(script.Parent.strict)
local Assets = {
Sprites = {},
Slices = {
RoundBox = {
asset = "rbxassetid://2773204550",
offset = Vector2.new(0, 0),
size = Vector2.new(32, 32),
center = Rect.new(4, 4, 4, 4),
RoundedBackground = {
Image = "rbxassetid://5981360418",
Center = Rect.new(10, 10, 10, 10),
Scale = 0.5,
},
RoundedBorder = {
Image = "rbxassetid://5981360137",
Center = Rect.new(10, 10, 10, 10),
Scale = 0.5,
},
},
Images = {
Logo = "rbxassetid://3405346157",
Icon = "rbxassetid://3405341609",
Logo = "rbxassetid://5990772764",
PluginButton = "rbxassetid://3405341609",
Icons = {
Close = "rbxassetid://6012985953",
Back = "rbxassetid://6017213752",
},
Checkbox = {
Active = "rbxassetid://6016251644",
Inactive = "rbxassetid://6016251963",
},
Spinner = {
Foreground = "rbxassetid://3222731032",
Background = "rbxassetid://3222730627",
},
ScrollBar = {
Top = "rbxassetid://6017290134",
Middle = "rbxassetid://6017289904",
Bottom = "rbxassetid://6017289712",
},
Circles = {
[16] = "rbxassetid://3056541177",
[32] = "rbxassetid://3088713341",
[64] = "rbxassetid://4918677124",
[128] = "rbxassetid://2600845734",
[500] = "rbxassetid://2609138523"
},
},
StartSession = "",
SessionActive = "",

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