Compare commits

...

142 Commits

Author SHA1 Message Date
Lucien Greathouse
e0198e626b Build Linux release on Ubuntu 20.04, use fixed artifact names 2022-08-20 21:34:41 -04:00
boatbomber
142705f386 Fix security permission error (#619) 2022-08-10 15:57:24 -04:00
boatbomber
4cb49c7825 Add sync locking for Team Create (#590)
* Add sync locking

* Steal lock from users who left without releasing

* Do not remove lock as unknown instance

* Don't delete non Archivable instance
2022-08-08 04:08:55 -04:00
Barocena
05adb82dda Renamed Common to Shared (#611) 2022-08-08 03:59:52 -04:00
boatbomber
faf7671799 Make error messages copyable (#614)
* Make error copyable

* Allow partial copying or double click full copy
2022-08-08 03:58:32 -04:00
Lucien Greathouse
d64db329dd Fix release workflow to use Wally 2022-08-08 00:28:52 -04:00
Lucien Greathouse
e34d2339ad Vendor OpenSSL via native-tls-vendored reqwest feature 2022-08-08 00:15:05 -04:00
Max
d196c5091c Simplify usage of attributes. (#574)
* Support implicit values for primitive attributes

This commit adds support for strings, numbers, and booleans to be implicitly typed in attribute maps, reducing the redundancy of needing to specify their types.

I also quietly adjusted one of the tests to use a more stable class/property pair. Since SourceAssetId is locked to Roblox, it could potentially disappear at any time.

* Apply formatting.

* Address feedback

* Backwards compatible format usage.

* Axe UnresolvedValueMap in favor of $attributes

Attributes can be defined directly on instances, with support for unambiguous types.

* Adjust test.

* to_string() -> into()

* Made attribute test more concise.

* small cleanup

* Update src/resolution.rs

* Update src/resolution.rs

* Update src/resolution.rs

* Update src/resolution.rs

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-08-03 20:07:06 -04:00
Lucien Greathouse
3e83f92532 Update MSRV to 1.58.1 for format string capturing 2022-08-03 19:39:36 -04:00
James Onnen
41d7aaf323 Add uipadding to notifications (#589) 2022-08-03 19:01:07 -04:00
boatbomber
e110f3726a Real-time status about sync details (#569)
* Rough prototype of patch info display

* Remove extra newline

* Switch to binding

* Update slower for older timestamps

* Batch patches within a second of each other

* Fix indentation

* Less wasteful refresh hz

* More apt variable name

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-08-03 18:58:28 -04:00
Boegie19
eb5c897ac0 fix relevant_paths not being set for init.csv (#599)
* fix relevant_paths not being set for init.csv

* fix failing tests

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-08-03 18:38:08 -04:00
boatbomber
e864cf0c7d Switch git submodules to Wally packages (#584)
* Switch git submodules to Wally packages

* Update build snapshot

* Add wally to foreman and use latest versions

* Install packages in CI runners

* Fix indents

* Install packages in the correct directory

* Install packages in correct dir of release action too

* Remove submodules from ci checkout

* Remove submodules from release checkout

* Update selene with latest fix

* Fix whitespace

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-08-03 18:36:58 -04:00
Lucien Greathouse
565c12405e Skip empty AppliedPatchSets for sending changes. 2022-08-03 17:19:23 -04:00
JohnnyMorganz
2a6a8b42a6 Add --watch to sourcemap generation (#602)
* Implement watch argument

* Add forget call

* Clippy fixes

* Update changelog
2022-08-01 04:07:07 -04:00
Boegie19
5cb4cc0d1d feature init csv (#594)
* init csv feature + test

* fmt fixes
2022-07-29 21:45:19 -04:00
boatbomber
62eb4f026f Fix errors after session already ended (#587) 2022-07-23 12:24:16 -04:00
wackbyte
411d1a89c1 Really default to the current directory in 'rojo fmt-project' (#581)
Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-07-18 19:47:30 -04:00
boatbomber
6ae0bf366a Use singleton settings outside the Roact tree (#576)
* Use singleton settings outside the Roact tree

* Cleanup listener on unmount

* Refactor setting page components

* Fix willUnmount being added to the wrong table

* Remove bindings in favor of state
2022-07-18 19:36:38 -04:00
wackbyte
178cdc9dfa Update benches so they compile (#582) 2022-07-17 18:50:12 -04:00
wackbyte
5bf1f86886 Fix link to v7.2.1 in the changelog (#578) 2022-07-11 00:44:47 -04:00
Lucien Greathouse
e482aba030 Release v7.2.1 2022-07-08 20:22:16 -04:00
Samuel P
535e4d42bb Change Notification sound to generic sound (#566)
* Change Notification sound to generic sound

The notification sound causes the game to summon an error due to no experience permissions with no way to grant permission. This is due to the new audio policy update.

* Update Notification sound
2022-07-02 19:33:24 -04:00
boatbomber
54398d4c4b Add setting to toggle sound effects (#568)
* Use soundPlayer object with setting

* Style changes
2022-07-02 05:12:58 -04:00
Lucien Greathouse
0987b44e23 Release v7.2.0 2022-06-29 20:34:06 -04:00
Lucien Greathouse
58098e96d4 Update Changelog 2022-06-29 20:15:24 -04:00
Max
f649c180cf Disambiguate camelCase and PascalCase in *.meta.json and *.model.json (#563)
* Disambiguate camelCase and PascalCase.

*.meta.json forces camelCase while *.model.json forces PascalCase. This commit reinforces camelCase as the preference for both, but allows for PascalCase in both as well.

* Made requested changes, breaking due to serde bug.

* Make work with existing Serde stuff

* Work around MSRV

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-06-29 20:14:35 -04:00
Lucien Greathouse
966478b131 Update Changelog 2022-06-29 19:07:19 -04:00
boatbomber
ca0759a011 Add notification popups (#540)
* Add notifications prototype

* Add timeout

* Improve function name

* Faster timeouts and fully clickable

* Update remove padding from old X button

* Only auto-dismiss when viewport is open

* Start auto dismiss once viewed

* Avoid redundantly displaying widget text as notifs

* Add sound effect

* Add setting for notifications

* Remove duplicate PluginSettings.StudioProvider

* Use short pop sound effect

* Fix broken audio, thanks Roblox

* Use e instead of createElement
2022-06-29 19:06:13 -04:00
Lucien Greathouse
f1cdf2fe79 Update CHANGELOG 2022-06-29 19:05:04 -04:00
Watermelon
04fa5e2719 Added address reference to CLI output (#556)
* Added address reference to CLI output

* Stored loopback check address as a variable

* Changed other loopback references to the new variable

* Fixed mistake on address_string variable

* Merge write calls

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-06-29 19:04:28 -04:00
Lucien Greathouse
eccb95690c Live sync Attributes (#553)
* Add test project for tags

* Update rbx_dom_lua and add attributes project

* Add Attributes shorthand; not working

* Update dependencies

* Update rbx_reflection_database

* Update rbx_types and commit attributes snapshot
2022-06-29 18:53:34 -04:00
Samuel P
acf7456371 Accept .luau files (#552)
* accept .luau files

* Accept .luau in snapshot creation

* Update versioning and snapshots.

* fix versioning

* Run rustfmt

* Reduce repetition in extension detection

* Tidy build script change

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-06-29 18:53:10 -04:00
Lucien Greathouse
8ea41480b7 Bump MSRV (#564)
* Bump MSRV

* Fix updated snapshot
2022-06-29 18:41:37 -04:00
Lucien Greathouse
0d1bc0d7fe Update dependencies 2022-06-29 18:00:50 -04:00
JohnnyMorganz
f9b7774286 Change linux release runner to use ubuntu-18.04 (#561)
ubuntu-latest uses Ubuntu 20.04, this causes issues with glibc as older versions of ubuntu/other distros use an older version.

This is fixed by building the release binary on `ubuntu-18.04`, which uses a version of glibc more widely available.

Ref: https://github.com/JohnnyMorganz/StyLua/pull/444
https://github.com/JohnnyMorganz/StyLua/pull/445
2022-06-26 15:22:16 -04:00
Michael Schmatz
2e672badf2 Update rbx_binary to 0.6.5 (#558) 2022-06-22 04:46:34 -04:00
Lucien Greathouse
cd5d6fd15c Add test project for tags 2022-06-12 05:05:17 -04:00
Micah
cf76982cfa Update selene (#550)
* Update selene

* Update foreman.toml

Co-authored-by: Sasial <44125644+sasial-dev@users.noreply.github.com>

Co-authored-by: Sasial <44125644+sasial-dev@users.noreply.github.com>
Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-06-12 03:41:49 -04:00
Micah
2624ea7d2a Remove .luacheckrc (#551) 2022-06-12 02:57:40 -04:00
Lucien Greathouse
2e7c4b6dff Update Changelog 2022-06-10 02:15:07 -04:00
Lucien Greathouse
e5dbee1073 Defer application of init.meta.json until after init Lua files. (#549)
Fixes #546.
2022-06-09 21:42:37 -04:00
Lucien Greathouse
c06463b61d Update CHANGELOG 2022-06-05 17:48:17 -04:00
Lucien Greathouse
10341e3776 Major Performance Improvements (#548)
* Use WeakDom::into_raw for faster snapshot generation from models

* Make compute_patch_set take snapshots by value

* Stop deferring property application in apply_patch_set

* Use InstanceBuilder::empty to avoid extra name allocations

* Git dependencies, skip dropping ServeSession

* Use std::mem::forget instead of ManuallyDrop

* Switch to latest rbx-dom crates.io dependencies

* Update other dependencies
2022-06-05 17:47:31 -04:00
Lucien Greathouse
824cdc5dcd Annotate snapshot_rbxm for profiling 2022-05-27 18:13:07 -04:00
Lucien Greathouse
7aa7a35aa5 Add profiling info and optional profiling with Tracy 2022-05-27 03:08:54 -04:00
Lucien Greathouse
79b57b3359 Move memofs and rojo-insta-ext into crates folder 2022-05-26 04:23:44 -04:00
Lucien Greathouse
c7aeffe586 Switch from structopt to clap 2022-05-26 04:19:51 -04:00
Lucien Greathouse
79c02f2457 Delete old bin folder and update foreman.toml 2022-05-26 04:13:50 -04:00
Lucien Greathouse
b9ed68fa9e Release v7.1.1 2022-05-26 02:53:20 -04:00
Lucien Greathouse
6c6d6c9c8d Add .github/FUNDING.yml 2022-05-26 02:28:57 -04:00
Lucien Greathouse
e169d7be68 New release workflow (#547)
* Port release workflow from Aftman to test

* Checkout submodules in plugin build step

* ...and build with submodules for other builds too

* Fix ci.yml; we use master branch still

* CI with submodules too
2022-05-25 22:26:22 -04:00
Lucien Greathouse
192fd7d4dd New and improved CI pipeline 2022-05-25 18:53:08 -04:00
Lucien Greathouse
1f1193e857 Remove unused lazy_static 2022-05-25 18:48:57 -04:00
boatbomber
0a412ade88 Remove duplicate PluginSettings.StudioProvider (#545) 2022-05-25 18:48:10 -04:00
Filip Tibell
3cef2fe9aa Fix sourcemap command not stripping paths correctly (#544)
* Fix sourcemap command not stripping paths correctly

* Use ServeSession to get the proper root dir to strip for sourcemap
2022-05-23 15:19:30 -04:00
Lucien Greathouse
18e53f06fe Remove unused dependencies and dead code warnings 2022-05-22 19:20:41 -04:00
Lucien Greathouse
eaac539087 Update to reqwest 0.11.10 2022-05-22 19:16:43 -04:00
Lucien Greathouse
57005c4fd5 Update uuid and winreg 2022-05-22 19:13:11 -04:00
Lucien Greathouse
ea58999a2a Update to pretty_assertions 1.2.1 2022-05-22 19:12:33 -04:00
Lucien Greathouse
a5a69fd9fc Release v7.1.0 2022-05-22 18:53:45 -04:00
boatbomber
f1d0f1c1c9 Bugfix: PluginAction spam causing errors (#541)
* Use session's state instead of existence to determine action

* Retain host/port text

* Use bindings instead of text/ref tunneling

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-05-22 18:47:11 -04:00
boatbomber
83492d7495 PluginActions for connecting/disconnecting a session (#537)
* Create plugin action component

* Add plugin action for session start/end

* Add output for connection status change

* Move host & port refs to App level so keybind can access them

* Use passed function directly

* Improve the action text clarity

* Add actions for single action

* Add to changelog

* Explicitly return nil

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>

* Change log level to info

* Refactor startSession to contain the logic

* Formatting

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-05-02 17:49:31 -04:00
boatbomber
10abc2254a Add changing toolbar icon to indicate state (#538)
* Add changing toolbar icon

* Return to default icon after closing error

* Update changelog

* Add assets

* Improved link icon

* Upload new icons

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-05-02 17:28:18 -04:00
Lucien Greathouse
5d5536a95e Update CHANGELOG 2022-04-19 18:45:09 -04:00
James Onnen
fe81e55925 Add support for optional paths (#472)
* Add PathNode with optional fields to project. This allows a path to be defined either as `"$path": "src"` or `"$path": { "optional": "src" }`

* Make $path truly optional

* Prevent rojo from erroring if no project node is resolved

* Use match instead of if-statement

* Add end-to-end tests (credit to MobiusCraftFlip for initial scenario)

* Pass option with ref inside instead of reference to option

* Empty commit to restart GitHub Actions

* Simplify build test

* Minimize serve test: it fails

* Simplify serve test even more

* Ignore failing serve test

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-04-19 18:43:47 -04:00
Micah
654690d73e Add testez std (#535) 2022-04-19 17:45:55 -04:00
Filip Tibell
256aba4bc1 Implement sourcemap CLI command (#530)
* Initial implementation of sourcemap CLI command

* Update src/cli/sourcemap.rs

Co-authored-by: JohnnyMorganz <johnnymorganz@outlook.com>

* Update src/cli/sourcemap.rs

Co-authored-by: JohnnyMorganz <johnnymorganz@outlook.com>

* Tidy up sourcemap command

* Update CHANGELOG

Co-authored-by: JohnnyMorganz <johnnymorganz@outlook.com>
Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-04-19 17:45:29 -04:00
Ashton Miller
49f8845105 Add ability to specify address in default.project.json (#507)
* Allow for setting the default port in project json

set as
```json
"serveAddress": "0.0.0.0"
```

* Update CHANGELOG.md

* cargo fmt

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-04-19 16:54:03 -04:00
Ashton Miller
12370846b4 Support the new Open Cloud API (#504)
* Add support for the new Open Cloud API

* Cleanup Open Cloud variables

* Avoid cloning buffer for do_upload_open_cloud

* Satisfy cargo fmt

* Actually correct cargo fmt

Apparently my earlier fix did not fix everything.

* Update CHANGELOG.md

* Update CHANGELOG.md

Forgot to add the link to issue #486 in the previous commit :/

* Cleanup & improve code for open cloud api

* Commit to force GH Actions to run (?)

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2022-04-19 16:02:40 -04:00
Lucien Greathouse
07637dfe96 Release v7.0.0 2021-12-10 19:37:39 -05:00
Lucien Greathouse
f389a4a1db Update rbx_dom_lua 2021-11-26 17:31:12 -05:00
Lucien Greathouse
af077c796c Update dependencies 2021-11-26 17:28:13 -05:00
Lucien Greathouse
1c319f2fa8 Promote weldconstraint test project to a build test 2021-11-26 16:36:55 -05:00
Lucien Greathouse
e8afa03f7b Fix most test output (but not termcolor) 2021-11-22 13:59:12 -05:00
Lucien Greathouse
9b22545842 Add tests for current file naming with regards to project name field 2021-11-22 13:22:16 -05:00
Lucien Greathouse
adc733d25c Update changelog 2021-11-20 18:14:17 -05:00
Lucien Greathouse
6896257647 Bump MSRV to 1.55.0 2021-11-20 18:07:24 -05:00
Lucien Greathouse
1d9845a6cb Update dependencies 2021-11-20 18:06:41 -05:00
Blake Mealey
8461339e9a Add note for git submodules (#495) 2021-11-20 17:53:22 -05:00
Umbreon
9904d94e4c Remember sync connection settings. (#500) 2021-11-20 17:51:38 -05:00
Lucien Greathouse
da25c80d0b Add support for CFrame shorthand. Fixes #430. 2021-11-20 17:50:40 -05:00
Lucien Greathouse
5fa63733fd Factor out property filtering code to simplify web server 2021-11-20 17:38:36 -05:00
Lucien Greathouse
8b54bf0ba1 Improve error when file is not found 2021-11-20 17:15:58 -05:00
Lucien Greathouse
173dc12cb3 Improve warning and debug output in plugin 2021-11-20 17:05:45 -05:00
Umbreon
e136529ff0 Add a check to getProperty for unknown properties. (#493) 2021-10-28 01:09:20 -04:00
Lucien Greathouse
75542dacb3 Release Rojo 7.0.0-rc.3 2021-10-19 17:12:28 -04:00
Lucien Greathouse
07abfbde43 Release Rojo 7.0.0-rc.2 2021-10-19 17:07:14 -04:00
Kenneth Loeffler
96112fe118 Add ambiguous value resolution StringArray -> Tags (#484)
* Add ambiguous value resolution StringArray -> Tags

* Remove funny autocompleted reference
2021-10-19 16:46:31 -04:00
Kenneth Loeffler
9d0b313261 Add ChangeBatcher to plugin for two-way sync (#478)
* Implement ChangeBatcher

* Use ChangeBatcher for two-way sync

* Pause updates during patch application

* I can English good

* Break after encountering a nil Parent change

This prevents __flush from erroring out when an instance's Parent is
changed to nil and it has other property changes in the same batch.

* Update rbx_dom_lua

* Don't connect changed listeners in a running game

 #468 made me realize how bad of an idea this is in general...

* Update TestEZ and fix sibling Ref reification test

* Add ChangeBatcher tests

* Test instance unpausing by breaking functionality out to __cycle

* Break up the module a bit and improve tests

* Shuffle requires around and edit comment

* Break out more stuff, rename createChangePatch -> createPatchSet

* Make ChangeBatcher responsible for unpausing all paused instances

This somewhat improves the situation (of course, it would preferrable
to not have to hack around this problem with Source at all). It also
sets us up nicely if we come across any other properties that do
anything similar.

* Remove old reference to pausedBatchInstances

* Use RenderStepped instead of Heartbeat and trash multi-frame pauses

I probably should have done this in the first place...

ChangeBatcher still needs to unpause instances, but we don't need to
hold pauses for any longer than one cycle.

* Remove useless branch

* if not next(x) -> if next(x) == nil

* Add InstanceMap:unpauseAllInstances, use it in ChangeBatcher

* Move IsRunning check to InstanceMap:__maybeFireInstanceChanged
2021-10-18 18:18:51 -04:00
Wiktor Rudnicki
277ddfa9be Themes colors modification (#482)
* Modified colors of themes

Colors match Roblox Studio theme.

* Change cases of colors

Colors' hexes have correct cases now.
2021-10-15 13:27:47 -04:00
Lucien Greathouse
5d88bdb256 Upgrade dependencies in lockfile 2021-10-15 13:24:40 -04:00
Lucien Greathouse
8d29b43155 Upgrade dependencies 2021-10-11 17:40:14 -04:00
Lucien Greathouse
cc071a6415 Move entrypoint from src/bin.rs to src/main.rs 2021-09-14 20:42:38 -04:00
Lucien Greathouse
8954def25c Move responsibility for extracting names from paths lower 2021-08-24 17:59:53 -04:00
Lucien Greathouse
d484098781 Get rid of confusing 'SnapshotInstanceResult' type alias 2021-08-24 17:15:47 -04:00
Lucien Greathouse
9f06cbf3a0 Update contributing guide 2021-08-23 16:11:56 -04:00
Lucien Greathouse
af4a3ca0af Update memofs to 0.2.0 2021-08-23 16:00:51 -04:00
Lucien Greathouse
43715143e4 Release v7.0.0-rc.1 2021-08-23 15:50:28 -04:00
Lucien Greathouse
16aa354d36 Update dependencies and fix Lua ref tests 2021-08-23 15:48:17 -04:00
Lucien Greathouse
c739025453 Update changelog 2021-08-23 15:23:19 -04:00
James Onnen
f0526d17de Support long file paths on Windows (past 256 limit) (#464)
* Support long file paths on Windows (past 256 limit)

This issue can occur when using symlinks deep in rojo such that very long paths can occur, among other scenarios.

Note while the original fix comes from here:
	https://gal.hagever.com/posts/windows-long-paths-in-rust/

The manifest had to be modified from this source:
	https://stackoverflow.com/questions/59816045/windows-10-1903-longpathaware-not-working

* Move manifests, tidy code a little

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2021-08-23 15:21:01 -04:00
Ayuka
6cc2e919c0 Update nil Ref check and property decode warning to new Rojo protocol (#466)
* Skip empty Refs in new Rojo protocol

* Update warning message for new Rojo protocol
2021-08-09 14:14:59 -04:00
Kenneth Loeffler
e1f9eaefa9 Fix Ref reification (#462)
* Update ApiValue type to use new value format

* Use new value format for Ref reification
2021-08-05 03:03:13 -04:00
Lucien Greathouse
5d62bf9b60 Upgrade to Tokio 1.x, futures 0.3, Hyper 0.14, etc (#459)
* Upgrade dependencies, oneshot channel ref

* New service style?

* Fix warning

* A server is running again

* Working server with async blocks

* UI working again

* Finish upgrade

* Bump MSRV to 1.46.0 for if/match in const fn

* Update the README as part of this
2021-07-28 12:29:46 -04:00
Lucien Greathouse
4aa5814a0a Update dependencies 2021-07-27 14:06:23 -04:00
Lucien Greathouse
5bca244062 Add test project for #458 2021-07-27 12:42:47 -04:00
Lucien Greathouse
7cf57714a4 Use new Cargo config.toml convention 2021-07-12 13:19:36 -04:00
Lucien Greathouse
92e6f862ad Update plugin to use new property format 2021-07-02 16:12:12 -04:00
Lucien Greathouse
2377f41036 Update to latest rbx-dom 2.0 dependencies, including Lua 2021-07-02 16:04:43 -04:00
Lucien Greathouse
26a08f4d9f Update Changelog 2021-06-29 01:26:29 -04:00
Lucien Greathouse
672d207961 Update to stable rbx-dom libraries 2021-06-29 01:20:09 -04:00
Lucien Greathouse
a3d8e50f26 Fix changelog bullets being in the wrong section 2021-06-18 16:47:52 -04:00
Lucien Greathouse
d3abca46a8 Fix deprecation warning by writing better code 2021-06-14 12:50:54 -04:00
Lucien Greathouse
17fdd18c55 Code docs 2021-06-11 22:19:50 -04:00
Lucien Greathouse
e482d038c6 Tests for value resolution, better errors, no more Color3uint8 2021-06-11 22:04:04 -04:00
Lucien Greathouse
d0482a004e Modernize upload command 2021-06-08 21:53:56 -04:00
Lucien Greathouse
561a3e3256 Modernize serve subcommand 2021-06-08 17:05:55 -04:00
Lucien Greathouse
158dac5e1c Move subcommand branching into Options struct 2021-06-08 16:53:03 -04:00
Lucien Greathouse
1413f8c0b6 Tidy up build command further 2021-06-08 16:50:21 -04:00
Lucien Greathouse
ffb2aa332a Modernize build subcommand 2021-06-08 16:48:20 -04:00
Lucien Greathouse
45e8208e9c Improve CLI help text 2021-06-08 15:37:59 -04:00
Lucien Greathouse
7f230a8bf4 Modernize the plugin subcommand 2021-05-21 13:09:07 -04:00
Lucien Greathouse
afe26b8c16 Modernize the doc subcommand 2021-05-21 12:45:07 -04:00
Lucien Greathouse
d153f62b8a Modernize the init subcommand 2021-05-20 17:34:45 -04:00
Lucien Greathouse
5c80cd6e50 Skip serializing place_id and game_id if null 2021-05-20 15:46:40 -04:00
Lucien Greathouse
df1aced95d Add fmt-project subcommand 2021-05-20 15:41:08 -04:00
Lucien Greathouse
5a0a8f5077 Release v7.0.0-alpha.4 2021-05-14 16:37:49 -04:00
Lucien Greathouse
1c281539e0 Update rbx_dom_lua, using subfolder now 2021-05-14 16:36:32 -04:00
Lucien Greathouse
ef41d14f50 Update changelog 2021-05-14 16:29:05 -04:00
Lucien Greathouse
aa29397732 Update changelog 2021-05-14 16:18:54 -04:00
Mixu78
ca8865a3ce Do not validate .model.json files with no content/whitespace only (#420)
* Ignore empty/whitespace-only model.json files

* Ignore no return value from model.json files during snapshot

* Use str::from_utf8 instead of String::from_utf8

* Revert "Ignore no return value from model.json files during snapshot"

This reverts commit 0aef16e30a.

* Add test for empty .model.json files

* Change empty .model.json check method

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>

* Format code with cargo fmt

* Use raw string instead

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2021-05-14 16:16:05 -04:00
Lucien Greathouse
7599ef6626 Improve error messages 2021-05-14 15:58:18 -04:00
Lucien Greathouse
43e71f9242 Upgrade Rojo to 6.1.0 (lol) 2021-05-14 15:09:19 -04:00
Lucien Greathouse
aac9b25efe Update dependencies 2021-05-14 15:08:48 -04:00
Lucien Greathouse
532d170585 Fix 'Open Scripts Externally' crashing studio.
Closes #369.
2021-04-23 16:59:59 -04:00
Lucien Greathouse
3dcb14013b Update changelog 2021-04-23 15:45:10 -04:00
Lucien Greathouse
a4c782cd35 Mark two-way sync as experimental in UI 2021-04-23 15:41:17 -04:00
Mixu78
0779baa0ac Block usage of "Name" or "Parent" in $properties (#413)
* Ignore usage of "Name" or "Parent" in $properties

* Use match instead of array

* Add changelog entry

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2021-04-23 15:11:17 -04:00
Lucien Greathouse
0849fab644 Commit snapshots from project file change 2021-04-23 14:34:41 -04:00
Mixu78
d85bca2e7e Reword top level model error message (#412) 2021-04-16 12:39:30 -04:00
Lucien Greathouse
c7ab6c435c Add gameId and placeId project properties 2021-04-15 14:27:35 -04:00
258 changed files with 20678 additions and 10292 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
patreon: lpghatguy

View File

@@ -11,29 +11,67 @@ on:
jobs: jobs:
build: build:
name: Build and Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
rust_version: [stable, "1.43.1"] rust_version: [stable, 1.58.1]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
with:
submodules: true
- name: Setup Rust toolchain - name: Install Rust
run: rustup default ${{ matrix.rust_version }} uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust_version }}
override: true
profile: minimal
- name: Setup Foreman
uses: Roblox/setup-foreman@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install packages
run: |
cd plugin
wally install
cd ..
- name: Build - name: Build
run: cargo build --locked --verbose run: cargo build --locked --verbose
- name: Run tests - name: Test
run: cargo test --locked --verbose run: cargo test --locked --verbose
- name: Rustfmt and Clippy lint:
name: Rustfmt and Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
components: rustfmt, clippy
- name: Setup Foreman
uses: Roblox/setup-foreman@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install packages
run: | run: |
cargo fmt -- --check cd plugin
cargo clippy wally install
if: matrix.rust_version == 'stable' cd ..
- name: Rustfmt
run: cargo fmt -- --check
- name: Clippy
run: cargo clippy

View File

@@ -2,65 +2,161 @@ name: Release
on: on:
push: push:
tags: ["*"] tags: ["v*"]
jobs: jobs:
windows: create-release:
runs-on: windows-latest name: Create Release
steps:
- uses: actions/checkout@v1
with:
submodules: true
- name: Build release binary
run: cargo build --verbose --locked --release
- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
name: rojo-win64
path: target/release/rojo.exe
macos:
runs-on: macos-latest
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
- name: Build release binary
run: |
source $HOME/.cargo/env
cargo build --verbose --locked --release
env:
OPENSSL_STATIC: 1
- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
name: rojo-macos
path: target/release/rojo
linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps: steps:
- uses: actions/checkout@v1 - name: Create Release
with: id: create_release
submodules: true uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: false
- name: Build build-plugin:
run: cargo build --locked --verbose --release needs: ["create-release"]
env: name: Build Roblox Studio Plugin
OPENSSL_STATIC: 1 runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Upload artifacts - name: Setup Foreman
uses: actions/upload-artifact@v1 uses: Roblox/setup-foreman@v1
with: with:
name: rojo-linux token: ${{ secrets.GITHUB_TOKEN }}
path: target/release/rojo
- name: Install packages
run: |
cd plugin
wally install
cd ..
- name: Build Plugin
run: rojo build plugin --output Rojo.rbxm
- name: Upload Plugin to Release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: Rojo.rbxm
asset_name: Rojo.rbxm
asset_content_type: application/octet-stream
- name: Upload Plugin to Artifacts
uses: actions/upload-artifact@v3
with:
name: Rojo.rbxm
path: Rojo.rbxm
build:
needs: ["create-release"]
strategy:
fail-fast: false
matrix:
# https://doc.rust-lang.org/rustc/platform-support.html
include:
- host: linux
os: ubuntu-20.04
target: x86_64-unknown-linux-gnu
label: linux-x86_64
- host: windows
os: windows-latest
target: x86_64-pc-windows-msvc
label: windows-x86_64
- host: macos
os: macos-latest
target: x86_64-apple-darwin
label: macos-x86_64
- host: macos
os: macos-latest
target: aarch64-apple-darwin
label: macos-aarch64
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
env:
BIN: rojo
steps:
- uses: actions/checkout@v3
- name: Get Version from Tag
shell: bash
# https://github.community/t/how-to-get-just-the-tag-name/16241/7#M1027
run: |
echo "PROJECT_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
echo "Version is: ${{ env.PROJECT_VERSION }}"
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
override: true
profile: minimal
- name: Setup Foreman
uses: Roblox/setup-foreman@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install packages
run: |
cd plugin
wally install
cd ..
- name: Build Release
run: cargo build --release --locked --verbose
env:
# Build into a known directory so we can find our build artifact more
# easily.
CARGO_TARGET_DIR: output
# On platforms that use OpenSSL, ensure it is statically linked to
# make binaries more portable.
OPENSSL_STATIC: 1
- name: Create Release Archive
shell: bash
run: |
mkdir staging
if [ "${{ matrix.host }}" = "windows" ]; then
cp "output/release/$BIN.exe" staging/
cd staging
7z a ../release.zip *
else
cp "output/release/$BIN" staging/
cd staging
zip ../release.zip *
fi
- name: Upload Archive to Release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: release.zip
asset_name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
asset_content_type: application/octet-stream
- name: Upload Archive to Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
path: release.zip

6
.gitignore vendored
View File

@@ -13,12 +13,12 @@
# Test places for the Roblox Studio Plugin # Test places for the Roblox Studio Plugin
/plugin/*.rbxlx /plugin/*.rbxlx
# Packages for the Roblox Studio Plugin
/plugin/*Packages
# Roblox Studio holds 'lock' files on places # Roblox Studio holds 'lock' files on places
*.rbxl.lock *.rbxl.lock
*.rbxlx.lock *.rbxlx.lock
# Snapshot files from the 'insta' Rust crate # 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

15
.gitmodules vendored
View File

@@ -1,15 +0,0 @@
[submodule "plugin/modules/roact"]
path = plugin/modules/roact
url = https://github.com/Roblox/roact.git
[submodule "plugin/modules/testez"]
path = plugin/modules/testez
url = https://github.com/Roblox/testez.git
[submodule "plugin/modules/promise"]
path = plugin/modules/promise
url = https://github.com/LPGhatguy/roblox-lua-promise.git
[submodule "plugin/modules/t"]
path = plugin/modules/t
url = https://github.com/osyrisrblx/t.git
[submodule "plugin/modules/flipper"]
path = plugin/modules/flipper
url = https://github.com/Reselim/Flipper

View File

@@ -1,58 +0,0 @@
stds.roblox = {
read_globals = {
game = {
other_fields = true,
},
-- Roblox globals
"script",
-- Extra functions
"tick", "warn", "spawn",
"wait", "settings", "typeof",
-- Types
"Vector2", "Vector3",
"Vector2int16", "Vector3int16",
"Color3",
"UDim", "UDim2",
"Rect",
"CFrame",
"Enum",
"Instance",
"DockWidgetPluginGuiInfo",
}
}
stds.plugin = {
read_globals = {
"plugin",
}
}
stds.testez = {
read_globals = {
"describe",
"it", "itFOCUS", "itSKIP", "itFIXME",
"FOCUS", "SKIP", "HACK_NO_XPCALL",
"expect",
}
}
ignore = {
"212", -- unused arguments
"421", -- shadowing local variable
"422", -- shadowing argument
"431", -- shadowing upvalue
"432", -- shadowing upvalue argument
}
std = "lua51+roblox"
files["**/*.server.lua"] = {
std = "+plugin",
}
files["**/*.spec.lua"] = {
std = "+testez",
}

View File

@@ -1,6 +1,163 @@
# Rojo Changelog # Rojo Changelog
## Unreleased Changes ## Unreleased Changes
* Added `--watch` flag to `rojo sourcemap` ([#602])
[#602]: https://github.com/rojo-rbx/rojo/pull/602
## [7.2.1] - July 8, 2022
* Fixed notification sound by changing it to a generic sound. ([#566])
* Added setting to turn off sound effects. ([#568])
[#566]: https://github.com/rojo-rbx/rojo/pull/566
[#568]: https://github.com/rojo-rbx/rojo/pull/568
[7.2.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.2.1
## [7.2.0] - June 29, 2022
* Added support for `.luau` files. ([#552])
* Added support for live syncing Attributes and Tags. ([#553])
* Added notification popups in the Roblox Studio plugin. ([#540])
* Fixed `init.meta.json` when used with `init.lua` and related files. ([#549])
* Fixed incorrect output when serving from a non-default address or port ([#556])
* Fixed Linux binaries not running on systems with older glibc. ([#561])
* Added `camelCase` casing for JSON models, deprecating `PascalCase` names. ([#563])
* Switched from structopt to clap for command line argument parsing.
* Significantly improved performance of building and serving. ([#548])
* Increased minimum supported Rust version to 1.57.0. ([#564])
[#540]: https://github.com/rojo-rbx/rojo/pull/540
[#548]: https://github.com/rojo-rbx/rojo/pull/548
[#549]: https://github.com/rojo-rbx/rojo/pull/549
[#552]: https://github.com/rojo-rbx/rojo/pull/552
[#553]: https://github.com/rojo-rbx/rojo/pull/553
[#556]: https://github.com/rojo-rbx/rojo/pull/556
[#561]: https://github.com/rojo-rbx/rojo/pull/561
[#563]: https://github.com/rojo-rbx/rojo/pull/563
[#564]: https://github.com/rojo-rbx/rojo/pull/564
[7.2.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.2.0
## [7.1.1] - May 26, 2022
* Fixed sourcemap command not stripping paths correctly ([#544])
* Fixed Studio plugin settings not saving correctly.
[#544]: https://github.com/rojo-rbx/rojo/pull/544
[#545]: https://github.com/rojo-rbx/rojo/pull/545
[7.1.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.1.1
## [7.1.0] - May 22, 2022
* Added support for specifying an address to be used by default in project files. ([#507])
* Added support for optional paths in project files. ([#472])
* Added support for the new Open Cloud API when uploading. ([#504])
* Added `sourcemap` command for generating sourcemaps to feed into other tools. ([#530])
* Added PluginActions for connecting/disconnecting a session ([#537])
* Added changing toolbar icon to indicate state ([#538])
[#472]: https://github.com/rojo-rbx/rojo/pull/472
[#504]: https://github.com/rojo-rbx/rojo/pull/504
[#507]: https://github.com/rojo-rbx/rojo/pull/507
[#530]: https://github.com/rojo-rbx/rojo/pull/530
[#537]: https://github.com/rojo-rbx/rojo/pull/537
[#538]: https://github.com/rojo-rbx/rojo/pull/538
[7.1.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.1.0
## [7.0.0] - December 10, 2021
* Fixed Rojo's interactions with properties enabled by FFlags that are not yet enabled. ([#493])
* Improved output in Roblox Studio plugin when bad property data is encountered.
* Reintroduced support for CFrame shorthand syntax in Rojo project and `.meta.json` files, matching Rojo 6. ([#430])
* Connection settings are now remembered when reconnecting in Roblox Studio. ([#500])
* Updated reflection database to Roblox v503.
[#430]: https://github.com/rojo-rbx/rojo/issues/430
[#493]: https://github.com/rojo-rbx/rojo/pull/493
[#500]: https://github.com/rojo-rbx/rojo/pull/500
[7.0.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0
## [7.0.0-rc.3] - October 19, 2021
This is the last release candidate for Rojo 7. In an effort to get Rojo 7 out the door, we'll be freezing features from here on out, something we should've done a couple months ago.
Expect to see Rojo 7 stable soon!
* Added support for writing `Tags` in project files, model files, and meta files. ([#484])
* Adjusted Studio plugin colors to match Roblox Studio palette. ([#482])
* Improved experimental two-way sync feature by batching changes. ([#478])
[#482]: https://github.com/rojo-rbx/rojo/pull/482
[#484]: https://github.com/rojo-rbx/rojo/pull/484
[#478]: https://github.com/rojo-rbx/rojo/pull/478
[7.0.0-rc.3]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-rc.3
## 7.0.0-rc.2 - October 19, 2021
(Botched release due to Git mishap, oops!)
## [7.0.0-rc.1] - August 23, 2021
In Rojo 6 and previous Rojo 7 alphas, an explicit Vector3 property would be written like this:
```json
{
"className": "Part",
"properties": {
"Position": {
"Type": "Vector3",
"Value": [1, 2, 3]
}
}
}
```
For Rojo 7, this will need to be changed to:
```json
{
"className": "Part",
"properties": {
"Position": {
"Vector3": [1, 2, 3]
}
}
}
```
The shorthand property format that most users use is not impacted. For reference, it looks like this:
```json
{
"className": "Part",
"properties": {
"Position": [1, 2, 3]
}
}
```
* Major breaking change: changed property syntax for project files; shorthand syntax is unchanged.
* Added the `fmt-project` subcommand for formatting Rojo project files.
* Improved error output for many subcommands.
* Updated to stable versions of rbx-dom libraries.
* Updated async infrastructure, which should fix a handful of bugs. ([#459])
* Fixed syncing refs in the Roblox Studio plugin ([#462], [#466])
* Added support for long paths on Windows. ([#464])
[#459]: https://github.com/rojo-rbx/rojo/pull/459
[#462]: https://github.com/rojo-rbx/rojo/pull/462
[#464]: https://github.com/rojo-rbx/rojo/pull/464
[#466]: https://github.com/rojo-rbx/rojo/pull/466
[7.0.0-rc.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-rc.1
## [7.0.0-alpha.4][7.0.0-alpha.4] (May 5, 2021)
* Added the `gameId` and `placeId` optional properties to project files.
* When connecting from the Rojo Roblox Studio plugin, Rojo will set the game and place ID of the current place to these values, if set.
* This is equivalent to running `game:SetUniverseId(...)` and `game:SetPlaceId(...)` from the command bar in Studio.
* Added "EXPERIMENTAL!" label to two-way sync toggle in Rojo's Roblox Studio plugin.
* Fixed `Name` and `Parent` properties being allowed in Rojo projects. ([#413][pr-413])
* Fixed "Open Scripts Externally" feature crashing Studio. ([#369][issue-369])
* Empty `.model.json` files will no longer cause errors. ([#420][pr-420])
* When specifying `$path` on a service, Rojo now keeps the correct class name. ([#331][issue-331])
* Improved error messages for misconfigured projects.
[issue-331]: https://github.com/rojo-rbx/rojo/issues/331
[issue-369]: https://github.com/rojo-rbx/rojo/issues/369
[pr-420]: https://github.com/rojo-rbx/rojo/pull/420
[pr-413]: https://github.com/rojo-rbx/rojo/pull/413
[7.0.0-alpha.4]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.4
## [7.0.0-alpha.3][7.0.0-alpha.3] (February 19, 2021) ## [7.0.0-alpha.3][7.0.0-alpha.3] (February 19, 2021)
* Updated dependencies, fixing `OptionalCoordinateFrame`-related issues. * Updated dependencies, fixing `OptionalCoordinateFrame`-related issues.

View File

@@ -29,25 +29,29 @@ Sometimes there's something that Rojo doesn't do that it probably should.
Please file issues and we'll try to help figure out what the best way forward is. Please file issues and we'll try to help figure out what the best way forward is.
## Local Development Gotchas
If your build fails with "Error: failed to open file `D:\code\rojo\plugin\modules\roact\src`" you need to update your Git submodules.
Run the command and try building again: `git submodule update --init --recursive`.
## Pushing a Rojo Release ## Pushing a Rojo Release
The Rojo release process is pretty manual right now. If you need to do it, here's how: The Rojo release process is pretty manual right now. If you need to do it, here's how:
1. Bump server version in [`Cargo.toml`](Cargo.toml) 1. Bump server version in [`Cargo.toml`](Cargo.toml)
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua) 2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
3. Run `cargo test` to update `Cargo.lock` and double-check tests 3. Run `cargo test` to update `Cargo.lock` and run tests
4. Update [`CHANGELOG.md`](CHANGELOG.md) 4. Update [`CHANGELOG.md`](CHANGELOG.md)
5. Commit! 5. Commit!
* `git add . && git commit -m "Release vX.Y.Z"` * `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` 6. Tag the commit
* `git tag vX.Y.Z`
7. Publish the CLI 7. Publish the CLI
* `cargo publish` * `cargo publish`
8. Publish the Plugin 8. Publish the Plugin
* `rojo publish plugin --asset_id 6415005344` * `cargo run -- upload plugin --asset_id 6415005344`
* `rojo build plugin -o Rojo.rbxm`
9. Push commits and tags 9. Push commits and tags
* `git push && git push --tags` * `git push && git push --tags`
10. Copy GitHub release content from previous release 10. Copy GitHub release content from previous release
* Update the leading text with a summary about the release * Update the leading text with a summary about the release
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md) * 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

2238
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
[package] [package]
name = "rojo" name = "rojo"
version = "7.0.0-alpha.3" version = "7.2.1"
rust-version = "1.58.1"
authors = ["Lucien Greathouse <me@lpghatguy.com>"] authors = ["Lucien Greathouse <me@lpghatguy.com>"]
description = "Enables professional-grade development tools for Roblox developers" description = "Enables professional-grade development tools for Roblox developers"
license = "MPL-2.0" license = "MPL-2.0"
@@ -8,7 +9,8 @@ homepage = "https://rojo.space"
documentation = "https://rojo.space/docs" documentation = "https://rojo.space/docs"
repository = "https://github.com/rojo-rbx/rojo" repository = "https://github.com/rojo-rbx/rojo"
readme = "README.md" readme = "README.md"
edition = "2018" edition = "2021"
build = "build.rs"
exclude = [ exclude = [
"/test-projects/**", "/test-projects/**",
@@ -26,26 +28,21 @@ default = []
# Enable this feature to live-reload assets from the web UI. # Enable this feature to live-reload assets from the web UI.
dev_live_assets = [] dev_live_assets = []
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client"]
[workspace] [workspace]
members = [ members = ["crates/*"]
"rojo-insta-ext",
"memofs",
]
[lib] [lib]
name = "librojo" name = "librojo"
path = "src/lib.rs" path = "src/lib.rs"
[[bin]]
name = "rojo"
path = "src/bin.rs"
[[bench]] [[bench]]
name = "build" name = "build"
harness = false harness = false
[dependencies] [dependencies]
memofs = { version = "0.1.2", path = "memofs" } memofs = { version = "0.2.0", path = "crates/memofs" }
# These dependencies can be uncommented when working on rbx-dom simultaneously # These dependencies can be uncommented when working on rbx-dom simultaneously
# rbx_binary = { path = "../rbx-dom/rbx_binary" } # rbx_binary = { path = "../rbx-dom/rbx_binary" }
@@ -54,61 +51,60 @@ memofs = { version = "0.1.2", path = "memofs" }
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" } # rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
# rbx_xml = { path = "../rbx-dom/rbx_xml" } # rbx_xml = { path = "../rbx-dom/rbx_xml" }
rbx_binary = "0.6.0-alpha.1" rbx_binary = "0.6.5"
rbx_dom_weak = "2.0.0-alpha.1" rbx_dom_weak = "2.4.0"
rbx_reflection = "4.0.0-alpha.1" rbx_reflection = "4.2.0"
rbx_reflection_database = "0.1.0" rbx_reflection_database = "0.2.2"
rbx_xml = "0.12.0-alpha.1" rbx_xml = "0.12.3"
anyhow = "1.0.27" anyhow = "1.0.44"
backtrace = "0.3" backtrace = "0.3.61"
bincode = "1.2.1" bincode = "1.3.3"
crossbeam-channel = "0.4.0" crossbeam-channel = "0.5.1"
csv = "1.1.1" csv = "1.1.6"
env_logger = "0.7.1" env_logger = "0.9.0"
fs-err = "2.2.0" fs-err = "2.6.0"
futures = "0.1.29" futures = "0.3.17"
globset = "0.4.4" globset = "0.4.8"
humantime = "1.3.0" humantime = "2.1.0"
hyper = "0.12.35" hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] }
jod-thread = "0.1.0" jod-thread = "0.1.2"
lazy_static = "1.4.0" log = "0.4.14"
log = "0.4.8" maplit = "1.0.2"
maplit = "1.0.1" notify = "4.0.17"
notify = "4.0.14" opener = "0.5.0"
opener = "0.4.1" reqwest = { version = "0.11.10", features = ["blocking", "json", "native-tls-vendored"] }
regex = "1.3.1"
reqwest = "0.9.20"
ritz = "0.1.0" ritz = "0.1.0"
rlua = "0.17.0" roblox_install = "1.0.0"
roblox_install = "0.2.2" serde = { version = "1.0.130", features = ["derive", "rc"] }
serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0.68"
serde_json = "1.0" termcolor = "1.1.2"
structopt = "0.3.5" thiserror = "1.0.30"
termcolor = "1.0.5" tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
thiserror = "1.0.11" uuid = { version = "1.0.0", features = ["v4", "serde"] }
tokio = "0.1.22" clap = { version = "3.1.18", features = ["derive"] }
uuid = { version = "0.8.1", features = ["v4", "serde"] } profiling = "1.0.6"
tracy-client = { version = "0.13.2", optional = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.6.2" winreg = "0.10.1"
[build-dependencies] [build-dependencies]
memofs = { version = "0.1.3", path = "memofs" } memofs = { version = "0.2.0", path = "crates/memofs" }
anyhow = "1.0.27" embed-resource = "1.6.4"
bincode = "1.2.1" anyhow = "1.0.44"
fs-err = "2.3.0" bincode = "1.3.3"
maplit = "1.0.1" fs-err = "2.6.0"
maplit = "1.0.2"
[dev-dependencies] [dev-dependencies]
rojo-insta-ext = { path = "rojo-insta-ext" } rojo-insta-ext = { path = "crates/rojo-insta-ext" }
criterion = "0.3" criterion = "0.3.5"
insta = { version = "1.3.0", features = ["redactions"] } insta = { version = "1.8.0", features = ["redactions"] }
lazy_static = "1.2" paste = "1.0.5"
paste = "0.1" pretty_assertions = "1.2.1"
pretty_assertions = "0.6.1" serde_yaml = "0.8.21"
serde_yaml = "0.8.9" tempfile = "3.2.0"
tempfile = "3.0" walkdir = "2.3.2"
walkdir = "2.1"

View File

@@ -1,21 +1,13 @@
<div align="center"> <div align="center">
<a href="https://rojo.space"> <a href="https://rojo.space"><img src="assets/logo-512.png" alt="Rojo" height="217" /></a>
<img src="assets/logo-512.png" alt="Rojo" height="217" />
</a>
</div> </div>
<div>&nbsp;</div> <div>&nbsp;</div>
<div align="center"> <div align="center">
<a href="https://github.com/rojo-rbx/rojo/actions"> <a href="https://github.com/rojo-rbx/rojo/actions"><img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" /></a>
<img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" /> <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> <a href="https://rojo.space/docs"><img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" /></a>
<a href="https://crates.io/crates/rojo">
<img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" />
</a>
<a href="https://rojo.space/docs">
<img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" />
</a>
</div> </div>
<hr /> <hr />
@@ -48,7 +40,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
Pull requests are welcome! Pull requests are welcome!
Rojo supports Rust 1.43.1 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has. Rojo supports Rust 1.58.1 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
## License ## License
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details. Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.

BIN
assets/NotificationPop.mp3 Normal file

Binary file not shown.

View File

@@ -4,7 +4,7 @@
"$className": "DataModel", "$className": "DataModel",
"ReplicatedStorage": { "ReplicatedStorage": {
"Common": { "Shared": {
"$path": "src/shared" "$path": "src/shared"
} }
}, },

BIN
assets/icon-link-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/icon-warn-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -3,7 +3,7 @@ use std::path::Path;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use tempfile::{tempdir, TempDir}; use tempfile::{tempdir, TempDir};
use librojo::cli::{build, BuildCommand}; use librojo::cli::BuildCommand;
pub fn benchmark_small_place(c: &mut Criterion) { pub fn benchmark_small_place(c: &mut Criterion) {
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place") bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
@@ -20,7 +20,7 @@ fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
group.bench_function("build", |b| { group.bench_function("build", |b| {
b.iter_batched( b.iter_batched(
|| place_setup(path), || place_setup(path),
|(_dir, options)| build(options).unwrap(), |(_dir, options)| options.run().unwrap(),
BatchSize::SmallInput, BatchSize::SmallInput,
) )
}); });

View File

@@ -1,5 +0,0 @@
#!/bin/sh
set -e
watchexec -c -w plugin "sh -c './bin/install-dev-plugin.sh'"

View File

@@ -1,13 +0,0 @@
#!/bin/sh
set -e
DIR="$( mktemp -d )"
PLUGIN_FILE="$DIR/Rojo.rbxm"
TESTEZ_FILE="$DIR/TestEZ.rbxm"
rojo build plugin -o "$PLUGIN_FILE"
rojo build plugin/testez.project.json -o "$TESTEZ_FILE"
remodel bin/mark-plugin-as-dev.lua "$PLUGIN_FILE" "$TESTEZ_FILE" 2>/dev/null
cp "$PLUGIN_FILE" "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"

View File

@@ -1,5 +0,0 @@
#!/bin/sh
set -e
rojo build plugin -o "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"

View File

@@ -1,12 +0,0 @@
local pluginPath, testezPath = ...
local plugin = remodel.readModelFile(pluginPath)[1]
local testez = remodel.readModelFile(testezPath)[1]
local marker = Instance.new("Folder")
marker.Name = "ROJO_DEV_BUILD"
marker.Parent = plugin
testez.Parent = plugin
remodel.writeModelFile(plugin, pluginPath)

View File

@@ -1,8 +0,0 @@
local pluginPath, placePath = ...
local plugin = remodel.readModelFile(pluginPath)[1]
local place = remodel.readPlaceFile(placePath)
plugin.Parent = place:GetService("ReplicatedStorage")
remodel.writePlaceFile(place, placePath)

View File

@@ -1,6 +0,0 @@
#!/bin/sh
set -e
./bin/run-cli-tests.sh
./bin/run-plugin-tests.sh

View File

@@ -1,9 +0,0 @@
#!/bin/sh
set -e
cargo test --all --locked
cargo fmt -- --check
touch src/lib.rs # Nudge Rust source to make Clippy actually check things
cargo clippy

View File

@@ -1,16 +0,0 @@
#!/bin/sh
set -e
DIR="$( mktemp -d )"
PLUGIN_FILE="$DIR/Rojo.rbxmx"
PLACE_FILE="$DIR/RojoTestPlace.rbxlx"
rojo build plugin -o "$PLUGIN_FILE"
rojo build plugin/place.project.json -o "$PLACE_FILE"
remodel bin/put-plugin-in-test-place.lua "$PLUGIN_FILE" "$PLACE_FILE"
run-in-roblox -s plugin/testBootstrap.server.lua "$PLACE_FILE"
luacheck plugin/src plugin/log plugin/http

View File

@@ -21,7 +21,7 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
// We can skip any TestEZ test files since they aren't necessary for // We can skip any TestEZ test files since they aren't necessary for
// the plugin to run. // the plugin to run.
if file_name.ends_with(".spec.lua") { if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
continue; continue;
} }
@@ -43,8 +43,6 @@ fn main() -> Result<(), anyhow::Error> {
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap(); let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
let plugin_root = PathBuf::from(root_dir).join("plugin"); let plugin_root = PathBuf::from(root_dir).join("plugin");
let plugin_modules = plugin_root.join("modules");
let snapshot = VfsSnapshot::dir(hashmap! { let snapshot = VfsSnapshot::dir(hashmap! {
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?, "default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
"fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?, "fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?,
@@ -52,20 +50,7 @@ fn main() -> Result<(), anyhow::Error> {
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?, "log" => snapshot_from_fs_path(&plugin_root.join("log"))?,
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?, "rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?, "src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
"modules" => VfsSnapshot::dir(hashmap! { "Packages" => snapshot_from_fs_path(&plugin_root.join("Packages"))?,
"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_path = Path::new(&out_dir).join("plugin.bincode");
@@ -73,5 +58,9 @@ fn main() -> Result<(), anyhow::Error> {
bincode::serialize_into(out_file, &snapshot)?; bincode::serialize_into(out_file, &snapshot)?;
println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc");
println!("cargo:rerun-if-changed=build/windows/rojo.manifest");
embed_resource::compile("build/windows/rojo-manifest.rc");
Ok(()) Ok(())
} }

View File

@@ -0,0 +1,2 @@
#define RT_MANIFEST 24
1 RT_MANIFEST "rojo.manifest"

View File

@@ -0,0 +1,8 @@
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
<ws2:longPathAware>true</ws2:longPathAware>
</windowsSettings>
</application>
</assembly>

View File

@@ -2,6 +2,9 @@
## Unreleased Changes ## Unreleased Changes
## 0.2.0 (2021-08-23)
* Updated to `crossbeam-channel` 0.5.1.
## 0.1.3 (2020-11-19) ## 0.1.3 (2020-11-19)
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching. * Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "memofs" name = "memofs"
description = "Virtual filesystem with configurable backends." description = "Virtual filesystem with configurable backends."
version = "0.1.3" version = "0.2.0"
authors = ["Lucien Greathouse <me@lpghatguy.com>"] authors = ["Lucien Greathouse <me@lpghatguy.com>"]
edition = "2018" edition = "2018"
readme = "README.md" readme = "README.md"
@@ -11,7 +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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
crossbeam-channel = "0.4.0" crossbeam-channel = "0.5.1"
fs-err = "2.3.0" fs-err = "2.3.0"
notify = "4.0.15" notify = "4.0.15"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@@ -1,3 +1,5 @@
[tools] [tools]
rojo = { source = "rojo-rbx/rojo", version = "6.0.0-rc.3" } rojo = { source = "rojo-rbx/rojo", version = "7.2.1" }
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" } run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
selene = { source = "Kampfkarren/selene", version = "0.20.0" }
wally = { source = "UpliftGames/wally", version = "0.3.1"}

View File

@@ -1,33 +1,25 @@
{ {
"name": "Rojo", "name": "Rojo",
"tree": { "tree": {
"$className": "Folder", "$className": "Folder",
"Plugin": { "Plugin": {
"$path": "src" "$path": "src"
}, },
"Log": { "Packages": {
"$path": "log" "$path": "Packages",
},
"Http": { "Log": {
"$path": "http" "$path": "log"
}, },
"Fmt": { "Http": {
"$path": "fmt" "$path": "http"
}, },
"RbxDom": { "Fmt": {
"$path": "rbx_dom_lua" "$path": "fmt"
}, },
"Roact": { "RbxDom": {
"$path": "modules/roact/src" "$path": "rbx_dom_lua"
}, }
"Promise": { }
"$path": "modules/promise/lib" }
},
"t": {
"$path": "modules/t/lib"
},
"Flipper": {
"$path": "modules/flipper/src"
}
}
} }

Submodule plugin/modules/t deleted from f643b50682

View File

@@ -1,44 +0,0 @@
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

@@ -23,8 +23,45 @@ end
local ALL_AXES = {"X", "Y", "Z"} local ALL_AXES = {"X", "Y", "Z"}
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"} local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
local EncodedValue = {}
local types local types
types = { types = {
Attributes = {
fromPod = function(pod)
local output = {}
for key, value in pairs(pod) do
local ok, result = EncodedValue.decode(value)
if ok then
output[key] = result
else
local warning = ("Could not decode attribute value of type %q: %s"):format(typeof(value), tostring(result))
warn(warning)
end
end
return output
end,
toPod = function(roblox)
local output = {}
for key, value in pairs(roblox) do
local ok, result = EncodedValue.encodeNaive(value)
if ok then
output[key] = result
else
local warning = ("Could not encode attribute value of type %q: %s"):format(typeof(value), tostring(result))
warn(warning)
end
end
return output
end,
},
Axes = { Axes = {
fromPod = function(pod) fromPod = function(pod)
local axes = {} local axes = {}
@@ -71,8 +108,8 @@ types = {
CFrame = { CFrame = {
fromPod = function(pod) fromPod = function(pod)
local pos = pod.Position local pos = pod.position
local orient = pod.Orientation local orient = pod.orientation
return CFrame.new( return CFrame.new(
pos[1], pos[2], pos[3], pos[1], pos[2], pos[3],
@@ -89,8 +126,8 @@ types = {
r20, r21, r22 = roblox:GetComponents() r20, r21, r22 = roblox:GetComponents()
return { return {
Position = {x, y, z}, position = {x, y, z},
Orientation = { orientation = {
{r00, r01, r02}, {r00, r01, r02},
{r10, r11, r12}, {r10, r11, r12},
{r20, r21, r22}, {r20, r21, r22},
@@ -123,10 +160,10 @@ types = {
fromPod = function(pod) fromPod = function(pod)
local keypoints = {} local keypoints = {}
for index, keypoint in ipairs(pod.Keypoints) do for index, keypoint in ipairs(pod.keypoints) do
keypoints[index] = ColorSequenceKeypoint.new( keypoints[index] = ColorSequenceKeypoint.new(
keypoint.Time, keypoint.time,
types.Color3.fromPod(keypoint.Color) types.Color3.fromPod(keypoint.color)
) )
end end
@@ -138,13 +175,13 @@ types = {
for index, keypoint in ipairs(roblox.Keypoints) do for index, keypoint in ipairs(roblox.Keypoints) do
keypoints[index] = { keypoints[index] = {
Time = keypoint.Time, time = keypoint.Time,
Color = types.Color3.toPod(keypoint.Value), color = types.Color3.toPod(keypoint.Value),
} }
end end
return { return {
Keypoints = keypoints, keypoints = keypoints,
} }
end, end,
}, },
@@ -223,11 +260,11 @@ types = {
fromPod = function(pod) fromPod = function(pod)
local keypoints = {} local keypoints = {}
for index, keypoint in ipairs(pod.Keypoints) do for index, keypoint in ipairs(pod.keypoints) do
keypoints[index] = NumberSequenceKeypoint.new( keypoints[index] = NumberSequenceKeypoint.new(
keypoint.Time, keypoint.time,
keypoint.Value, keypoint.value,
keypoint.Envelope keypoint.envelope
) )
end end
@@ -239,14 +276,14 @@ types = {
for index, keypoint in ipairs(roblox.Keypoints) do for index, keypoint in ipairs(roblox.Keypoints) do
keypoints[index] = { keypoints[index] = {
Time = keypoint.Time, time = keypoint.Time,
Value = keypoint.Value, value = keypoint.Value,
Envelope = keypoint.Envelope, envelope = keypoint.Envelope,
} }
end end
return { return {
Keypoints = keypoints, keypoints = keypoints,
} }
end, end,
}, },
@@ -257,11 +294,11 @@ types = {
return nil return nil
else else
return PhysicalProperties.new( return PhysicalProperties.new(
pod.Density, pod.density,
pod.Friction, pod.friction,
pod.Elasticity, pod.elasticity,
pod.FrictionWeight, pod.frictionWeight,
pod.ElasticityWeight pod.elasticityWeight
) )
end end
end, end,
@@ -271,11 +308,11 @@ types = {
return "Default" return "Default"
else else
return { return {
Density = roblox.Density, density = roblox.Density,
Friction = roblox.Friction, friction = roblox.Friction,
Elasticity = roblox.Elasticity, elasticity = roblox.Elasticity,
FrictionWeight = roblox.FrictionWeight, frictionWeight = roblox.FrictionWeight,
ElasticityWeight = roblox.ElasticityWeight, elasticityWeight = roblox.ElasticityWeight,
} }
end end
end, end,
@@ -284,15 +321,15 @@ types = {
Ray = { Ray = {
fromPod = function(pod) fromPod = function(pod)
return Ray.new( return Ray.new(
types.Vector3.fromPod(pod.Origin), types.Vector3.fromPod(pod.origin),
types.Vector3.fromPod(pod.Direction) types.Vector3.fromPod(pod.direction)
) )
end, end,
toPod = function(roblox) toPod = function(roblox)
return { return {
Origin = types.Vector3.toPod(roblox.Origin), origin = types.Vector3.toPod(roblox.Origin),
Direction = types.Vector3.toPod(roblox.Direction), direction = types.Vector3.toPod(roblox.Direction),
} }
end, end,
}, },
@@ -388,6 +425,11 @@ types = {
end, end,
}, },
Tags = {
fromPod = identity,
toPod = identity,
},
Vector2 = { Vector2 = {
fromPod = unpackDecoder(Vector2.new), fromPod = unpackDecoder(Vector2.new),
@@ -428,15 +470,15 @@ types = {
}, },
} }
local EncodedValue = {}
function EncodedValue.decode(encodedValue) function EncodedValue.decode(encodedValue)
local typeImpl = types[encodedValue.Type] local ty, value = next(encodedValue)
local typeImpl = types[ty]
if typeImpl == nil then if typeImpl == nil then
return false, "Couldn't decode value " .. tostring(encodedValue.Type) return false, "Couldn't decode value " .. tostring(ty)
end end
return true, typeImpl.fromPod(encodedValue.Value) return true, typeImpl.fromPod(value)
end end
function EncodedValue.encode(rbxValue, propertyType) function EncodedValue.encode(rbxValue, propertyType)
@@ -448,9 +490,23 @@ function EncodedValue.encode(rbxValue, propertyType)
end end
return true, { return true, {
Type = propertyType, [propertyType] = typeImpl.toPod(rbxValue),
Value = typeImpl.toPod(rbxValue),
} }
end end
local propertyTypeRenames = {
number = "Float64",
boolean = "Bool",
string = "String",
}
function EncodedValue.encodeNaive(rbxValue)
local propertyType = typeof(rbxValue)
if propertyTypeRenames[propertyType] ~= nil then
propertyType = propertyTypeRenames[propertyType]
end
return EncodedValue.encode(rbxValue, propertyType)
end
return EncodedValue return EncodedValue

View File

@@ -20,7 +20,21 @@ local function set(container, key, value)
end end
function PropertyDescriptor.fromRaw(data, className, propertyName) function PropertyDescriptor.fromRaw(data, className, propertyName)
local key, value = next(data.DataType)
return setmetatable({ return setmetatable({
-- The meanings of the key and value in DataType differ when the type of
-- the property is Enum. When the property is of type Enum, the key is
-- the name of the type:
--
-- { Enum = "<name of enum>" }
--
-- When the property is not of type Enum, the value is the name of the
-- type:
--
-- { Value = "<data type>" }
dataType = key == "Enum" and key or value,
scriptability = data.Scriptability, scriptability = data.Scriptability,
className = className, className = className,
name = propertyName, name = propertyName,

View File

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

View File

@@ -1,8 +1,76 @@
{ {
"Attributes": {
"value": {
"Attributes": {
"TestBool": {
"Bool": true
},
"TestBrickColor": {
"BrickColor": 24
},
"TestColor3": {
"Color3": [
1.0,
0.5,
0.0
]
},
"TestNumber": {
"Float64": 1337.0
},
"TestRect": {
"Rect": [
[
1.0,
2.0
],
[
3.0,
4.0
]
]
},
"TestString": {
"String": "Test"
},
"TestUDim": {
"UDim": [
1.0,
2
]
},
"TestUDim2": {
"UDim2": [
[
1.0,
2
],
[
3.0,
4
]
]
},
"TestVector2": {
"Vector2": [
1.0,
2.0
]
},
"TestVector3": {
"Vector3": [
1.0,
2.0,
3.0
]
}
}
},
"ty": "Attributes"
},
"Axes": { "Axes": {
"value": { "value": {
"Type": "Axes", "Axes": [
"Value": [
"X", "X",
"Y", "Y",
"Z" "Z"
@@ -12,35 +80,31 @@
}, },
"BinaryString": { "BinaryString": {
"value": { "value": {
"Type": "BinaryString", "BinaryString": "SGVsbG8h"
"Value": "SGVsbG8h"
}, },
"ty": "BinaryString" "ty": "BinaryString"
}, },
"Bool": { "Bool": {
"value": { "value": {
"Type": "Bool", "Bool": true
"Value": true
}, },
"ty": "Bool" "ty": "Bool"
}, },
"BrickColor": { "BrickColor": {
"value": { "value": {
"Type": "BrickColor", "BrickColor": 1004
"Value": 1004
}, },
"ty": "BrickColor" "ty": "BrickColor"
}, },
"CFrame": { "CFrame": {
"value": { "value": {
"Type": "CFrame", "CFrame": {
"Value": { "position": [
"Position": [
1.0, 1.0,
2.0, 2.0,
3.0 3.0
], ],
"Orientation": [ "orientation": [
[ [
4.0, 4.0,
5.0, 5.0,
@@ -63,8 +127,7 @@
}, },
"Color3": { "Color3": {
"value": { "value": {
"Type": "Color3", "Color3": [
"Value": [
1.0, 1.0,
2.0, 2.0,
3.0 3.0
@@ -74,8 +137,7 @@
}, },
"Color3uint8": { "Color3uint8": {
"value": { "value": {
"Type": "Color3uint8", "Color3uint8": [
"Value": [
0, 0,
128, 128,
255 255
@@ -85,20 +147,19 @@
}, },
"ColorSequence": { "ColorSequence": {
"value": { "value": {
"Type": "ColorSequence", "ColorSequence": {
"Value": { "keypoints": [
"Keypoints": [
{ {
"Time": 0.0, "time": 0.0,
"Color": [ "color": [
1.0, 1.0,
1.0, 1.0,
0.5 0.5
] ]
}, },
{ {
"Time": 1.0, "time": 1.0,
"Color": [ "color": [
0.0, 0.0,
0.0, 0.0,
0.0 0.0
@@ -111,22 +172,19 @@
}, },
"Content": { "Content": {
"value": { "value": {
"Type": "Content", "Content": "rbxassetid://12345"
"Value": "rbxassetid://12345"
}, },
"ty": "Content" "ty": "Content"
}, },
"Enum": { "Enum": {
"value": { "value": {
"Type": "Enum", "Enum": 1234
"Value": 1234
}, },
"ty": "Enum" "ty": "Enum"
}, },
"Faces": { "Faces": {
"value": { "value": {
"Type": "Faces", "Faces": [
"Value": [
"Right", "Right",
"Top", "Top",
"Back", "Back",
@@ -139,36 +197,31 @@
}, },
"Float32": { "Float32": {
"value": { "value": {
"Type": "Float32", "Float32": 15.0
"Value": 15.0
}, },
"ty": "Float32" "ty": "Float32"
}, },
"Float64": { "Float64": {
"value": { "value": {
"Type": "Float64", "Float64": 15123.0
"Value": 15123.0
}, },
"ty": "Float64" "ty": "Float64"
}, },
"Int32": { "Int32": {
"value": { "value": {
"Type": "Int32", "Int32": 6014
"Value": 6014
}, },
"ty": "Int32" "ty": "Int32"
}, },
"Int64": { "Int64": {
"value": { "value": {
"Type": "Int64", "Int64": 23491023
"Value": 23491023
}, },
"ty": "Int64" "ty": "Int64"
}, },
"NumberRange": { "NumberRange": {
"value": { "value": {
"Type": "NumberRange", "NumberRange": [
"Value": [
-36.0, -36.0,
94.0 94.0
] ]
@@ -177,18 +230,17 @@
}, },
"NumberSequence": { "NumberSequence": {
"value": { "value": {
"Type": "NumberSequence", "NumberSequence": {
"Value": { "keypoints": [
"Keypoints": [
{ {
"Time": 0.0, "time": 0.0,
"Value": 5.0, "value": 5.0,
"Envelope": 2.0 "envelope": 2.0
}, },
{ {
"Time": 1.0, "time": 1.0,
"Value": 22.0, "value": 22.0,
"Envelope": 0.0 "envelope": 0.0
} }
] ]
} }
@@ -197,34 +249,31 @@
}, },
"PhysicalProperties-Custom": { "PhysicalProperties-Custom": {
"value": { "value": {
"Type": "PhysicalProperties", "PhysicalProperties": {
"Value": { "density": 0.5,
"Density": 0.5, "friction": 1.0,
"Friction": 1.0, "elasticity": 0.0,
"Elasticity": 0.0, "frictionWeight": 50.0,
"FrictionWeight": 50.0, "elasticityWeight": 25.0
"ElasticityWeight": 25.0
} }
}, },
"ty": "PhysicalProperties" "ty": "PhysicalProperties"
}, },
"PhysicalProperties-Default": { "PhysicalProperties-Default": {
"value": { "value": {
"Type": "PhysicalProperties", "PhysicalProperties": "Default"
"Value": "Default"
}, },
"ty": "PhysicalProperties" "ty": "PhysicalProperties"
}, },
"Ray": { "Ray": {
"value": { "value": {
"Type": "Ray", "Ray": {
"Value": { "origin": [
"Origin": [
1.0, 1.0,
2.0, 2.0,
3.0 3.0
], ],
"Direction": [ "direction": [
4.0, 4.0,
5.0, 5.0,
6.0 6.0
@@ -235,8 +284,7 @@
}, },
"Rect": { "Rect": {
"value": { "value": {
"Type": "Rect", "Rect": [
"Value": [
[ [
0.0, 0.0,
5.0 5.0
@@ -251,8 +299,7 @@
}, },
"Region3int16": { "Region3int16": {
"value": { "value": {
"Type": "Region3int16", "Region3int16": [
"Value": [
[ [
-10, -10,
-5, -5,
@@ -269,15 +316,23 @@
}, },
"String": { "String": {
"value": { "value": {
"Type": "String", "String": "Hello, world!"
"Value": "Hello, world!"
}, },
"ty": "String" "ty": "String"
}, },
"Tags": {
"value": {
"Tags": [
"foo",
"con'fusion?!",
"bar"
]
},
"ty": "Tags"
},
"UDim": { "UDim": {
"value": { "value": {
"Type": "UDim", "UDim": [
"Value": [
1.0, 1.0,
32 32
] ]
@@ -286,8 +341,7 @@
}, },
"UDim2": { "UDim2": {
"value": { "value": {
"Type": "UDim2", "UDim2": [
"Value": [
[ [
-1.0, -1.0,
100 100
@@ -302,8 +356,7 @@
}, },
"Vector2": { "Vector2": {
"value": { "value": {
"Type": "Vector2", "Vector2": [
"Value": [
-50.0, -50.0,
50.0 50.0
] ]
@@ -312,8 +365,7 @@
}, },
"Vector2int16": { "Vector2int16": {
"value": { "value": {
"Type": "Vector2int16", "Vector2int16": [
"Value": [
-300, -300,
300 300
] ]
@@ -322,8 +374,7 @@
}, },
"Vector3": { "Vector3": {
"value": { "value": {
"Type": "Vector3", "Vector3": [
"Value": [
-300.0, -300.0,
0.0, 0.0,
1500.0 1500.0
@@ -333,8 +384,7 @@
}, },
"Vector3int16": { "Vector3int16": {
"value": { "value": {
"Type": "Vector3int16", "Vector3int16": [
"Value": [
60, 60,
37, 37,
-450 -450

View File

@@ -5,13 +5,31 @@ local CollectionService = game:GetService("CollectionService")
-- The reflection database refers to these as having scriptability = "Custom" -- The reflection database refers to these as having scriptability = "Custom"
return { return {
Instance = { Instance = {
Tags = { Attributes = {
read = function(instance, key) read = function(instance)
local tagList = CollectionService:GetTags(instance) return true, instance:GetAttributes()
return true, table.concat(tagList, "\0")
end, end,
write = function(instance, key, value) write = function(instance, _, value)
local existing = instance:GetAttributes()
for key, attr in pairs(value) do
instance:SetAttribute(key, attr)
end
for key in pairs(existing) do
if value[key] == nil then
instance:SetAttribute(key, nil)
end
end
return true
end,
},
Tags = {
read = function(instance)
return true, CollectionService:GetTags(instance)
end,
write = function(instance, _, value)
local existingTags = CollectionService:GetTags(instance) local existingTags = CollectionService:GetTags(instance)
local unseenTags = {} local unseenTags = {}
@@ -19,8 +37,7 @@ return {
unseenTags[tag] = true unseenTags[tag] = true
end end
local tagList = string.split(value, "\0") for _, tag in ipairs(value) do
for _, tag in ipairs(tagList) do
unseenTags[tag] = nil unseenTags[tag] = nil
CollectionService:AddTag(instance, tag) CollectionService:AddTag(instance, tag)
end end

View File

@@ -22,7 +22,7 @@ local function findCanonicalPropertyDescriptor(className, propertyName)
local aliasData = propertyData.Kind.Alias local aliasData = propertyData.Kind.Alias
if aliasData ~= nil then if aliasData ~= nil then
return PropertyDescriptor.fromRaw( return PropertyDescriptor.fromRaw(
currentClass.properties[aliasData.AliasFor], currentClass.Properties[aliasData.AliasFor],
currentClassName, currentClassName,
aliasData.AliasFor) aliasData.AliasFor)
end end

View File

@@ -1 +0,0 @@
require(game.ReplicatedStorage.TestEZ).TestBootstrap:run({game.ReplicatedStorage.RbxDom})

View File

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

View File

@@ -1,35 +0,0 @@
{
"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": "run-tests.lua"
}
},
"Players": {
"$className": "Players",
"$properties": {
"CharacterAutoLoads": false
}
},
"HttpService": {
"$className": "HttpService",
"$properties": {
"HttpEnabled": true
}
}
}
}

View File

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

View File

@@ -1,6 +1,7 @@
local Http = require(script.Parent.Parent.Http) local Packages = script.Parent.Parent.Packages
local Log = require(script.Parent.Parent.Log) local Http = require(Packages.Http)
local Promise = require(script.Parent.Parent.Promise) local Log = require(Packages.Log)
local Promise = require(Packages.Promise)
local Config = require(script.Parent.Config) local Config = require(script.Parent.Config)
local Types = require(script.Parent.Types) local Types = require(script.Parent.Types)
@@ -85,7 +86,7 @@ local ApiContext = {}
ApiContext.__index = ApiContext ApiContext.__index = ApiContext
function ApiContext.new(baseUrl) function ApiContext.new(baseUrl)
assert(type(baseUrl) == "string") assert(type(baseUrl) == "string", "baseUrl must be a string")
local self = { local self = {
__baseUrl = baseUrl, __baseUrl = baseUrl,

View File

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)

View File

@@ -1,8 +1,9 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Flipper = require(Rojo.Flipper) local Flipper = require(Packages.Flipper)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)

View File

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)

View File

@@ -1,8 +1,9 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Flipper = require(Rojo.Flipper) local Flipper = require(Packages.Flipper)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local bindingUtil = require(Plugin.App.bindingUtil) local bindingUtil = require(Plugin.App.bindingUtil)

View File

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)

View File

@@ -1,6 +1,7 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local e = Roact.createElement local e = Roact.createElement

View File

@@ -2,8 +2,9 @@ local RunService = game:GetService("RunService")
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)

View File

@@ -0,0 +1,41 @@
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local Dictionary = require(Plugin.Dictionary)
local StudioPluginContext = require(script.Parent.StudioPluginContext)
local e = Roact.createElement
local StudioPluginAction = Roact.Component:extend("StudioPluginAction")
function StudioPluginAction:init()
self.pluginAction = self.props.plugin:CreatePluginAction(
self.props.name, self.props.title, self.props.description, self.props.icon, self.props.bindable
)
self.pluginAction.Triggered:Connect(self.props.onTriggered)
end
function StudioPluginAction:render()
return nil
end
function StudioPluginAction:willUnmount()
self.pluginAction:Destroy()
end
local function StudioPluginActionWrapper(props)
return e(StudioPluginContext.Consumer, {
render = function(plugin)
return e(StudioPluginAction, Dictionary.merge(props, {
plugin = plugin,
}))
end,
})
end
return StudioPluginActionWrapper

View File

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

View File

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)

View File

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)
@@ -44,6 +45,10 @@ function StudioToggleButton:didUpdate(lastProps)
self.button.Enabled = self.props.enabled self.button.Enabled = self.props.enabled
end end
if self.props.icon ~= lastProps.icon then
self.button.Icon = self.props.icon
end
if self.props.active ~= lastProps.active then if self.props.active ~= lastProps.active then
self.button:SetActive(self.props.active) self.button:SetActive(self.props.active)
end end

View File

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)

View File

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

View File

@@ -2,9 +2,10 @@ local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Flipper = require(Rojo.Flipper) local Flipper = require(Packages.Flipper)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)

View File

@@ -1,8 +1,9 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Flipper = require(Rojo.Flipper) local Flipper = require(Packages.Flipper)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local bindingUtil = require(Plugin.App.bindingUtil) local bindingUtil = require(Plugin.App.bindingUtil)

View File

@@ -0,0 +1,199 @@
local TextService = game:GetService("TextService")
local StudioService = game:GetService("StudioService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local Flipper = require(Packages.Flipper)
local bindingUtil = require(script.Parent.bindingUtil)
local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
local baseClock = DateTime.now().UnixTimestampMillis
local e = Roact.createElement
local Notification = Roact.Component:extend("Notification")
function Notification:init()
self.motor = Flipper.SingleMotor.new(0)
self.binding = bindingUtil.fromMotor(self.motor)
self.lifetime = self.props.timeout
self.motor:onStep(function(value)
if value <= 0 then
if self.props.onClose then
self.props.onClose()
end
end
end)
end
function Notification:dismiss()
self.motor:setGoal(
Flipper.Spring.new(0, {
frequency = 5,
dampingRatio = 1,
})
)
end
function Notification:didMount()
self.motor:setGoal(
Flipper.Spring.new(1, {
frequency = 3,
dampingRatio = 1,
})
)
self.props.soundPlayer:play(Assets.Sounds.Notification)
self.timeout = task.spawn(function()
local clock = os.clock()
local seen = false
while task.wait(1/10) do
local now = os.clock()
local dt = now - clock
clock = now
if not seen then
seen = StudioService.ActiveScript == nil
end
if not seen then
-- Don't run down timer before being viewed
continue
end
self.lifetime -= dt
if self.lifetime <= 0 then
self:dismiss()
break
end
end
end)
end
function Notification:willUnmount()
task.cancel(self.timeout)
end
function Notification:render()
local time = DateTime.fromUnixTimestampMillis(self.props.timestamp)
local textBounds = TextService:GetTextSize(
self.props.text,
15,
Enum.Font.GothamSemibold,
Vector2.new(350, 700)
)
local transparency = self.binding:map(function(value)
return 1 - value
end)
local size = self.binding:map(function(value)
return UDim2.fromOffset(
(35+40+textBounds.X)*value,
math.max(14+20+textBounds.Y, 32+20)
)
end)
return Theme.with(function(theme)
return e("TextButton", {
BackgroundTransparency = 1,
Size = size,
LayoutOrder = self.props.layoutOrder,
Text = "",
ClipsDescendants = true,
[Roact.Event.Activated] = function()
self:dismiss()
end,
}, {
e(BorderedContainer, {
transparency = transparency,
size = UDim2.new(1, 0, 1, 0),
}, {
TextContainer = e("Frame", {
Size = UDim2.new(0, 35+textBounds.X, 1, -20),
Position = UDim2.new(0, 0, 0, 10),
BackgroundTransparency = 1
}, {
Logo = e("ImageLabel", {
ImageTransparency = transparency,
Image = Assets.Images.PluginButton,
BackgroundTransparency = 1,
Size = UDim2.new(0, 32, 0, 32),
Position = UDim2.new(0, 0, 0.5, 0),
AnchorPoint = Vector2.new(0, 0.5),
}),
Info = e("TextLabel", {
Text = self.props.text,
Font = Enum.Font.GothamSemibold,
TextSize = 15,
TextColor3 = theme.Notification.InfoColor,
TextTransparency = transparency,
TextXAlignment = Enum.TextXAlignment.Left,
TextWrapped = true,
Size = UDim2.new(0, textBounds.X, 0, textBounds.Y),
Position = UDim2.fromOffset(35, 0),
LayoutOrder = 1,
BackgroundTransparency = 1,
}),
Time = e("TextLabel", {
Text = time:FormatLocalTime("LTS", "en-us"),
Font = Enum.Font.Code,
TextSize = 12,
TextColor3 = theme.Notification.InfoColor,
TextTransparency = transparency,
TextXAlignment = Enum.TextXAlignment.Left,
Size = UDim2.new(1, -35, 0, 14),
Position = UDim2.new(0, 35, 1, -14),
LayoutOrder = 1,
BackgroundTransparency = 1,
}),
}),
Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, 17),
PaddingRight = UDim.new(0, 15),
}),
})
})
end)
end
local Notifications = Roact.Component:extend("Notifications")
function Notifications:render()
local notifs = {}
for index, notif in ipairs(self.props.notifications) do
notifs[notif] = e(Notification, {
soundPlayer = self.props.soundPlayer,
text = notif.text,
timestamp = notif.timestamp,
timeout = notif.timeout,
layoutOrder = (notif.timestamp - baseClock),
onClose = function()
self.props.onClose(index)
end,
})
end
return Roact.createFragment(notifs)
end
return Notifications

View File

@@ -1,8 +1,9 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Flipper = require(Rojo.Flipper) local Flipper = require(Packages.Flipper)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)

View File

@@ -1,121 +0,0 @@
--[[
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

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
@@ -12,6 +13,26 @@ local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
local e = Roact.createElement local e = Roact.createElement
local AGE_UNITS = { {31556909, "year"}, {2629743, "month"}, {604800, "week"}, {86400, "day"}, {3600, "hour"}, {60, "minute"}, }
function timeSinceText(elapsed: number): string
if elapsed < 3 then
return "just now"
end
local ageText = string.format("%d seconds ago", elapsed)
for _,UnitData in ipairs(AGE_UNITS) do
local UnitSeconds, UnitName = UnitData[1], UnitData[2]
if elapsed > UnitSeconds then
local c = math.floor(elapsed/UnitSeconds)
ageText = string.format("%d %s%s ago", c, UnitName, c>1 and "s" or "")
break
end
end
return ageText
end
local function ConnectionDetails(props) local function ConnectionDetails(props)
return Theme.with(function(theme) return Theme.with(function(theme)
return e(BorderedContainer, { return e(BorderedContainer, {
@@ -82,33 +103,59 @@ end
local ConnectedPage = Roact.Component:extend("ConnectedPage") local ConnectedPage = Roact.Component:extend("ConnectedPage")
function ConnectedPage:render() function ConnectedPage:render()
return Roact.createFragment({ return Theme.with(function(theme)
Header = e(Header, { return Roact.createFragment({
transparency = self.props.transparency, Header = e(Header, {
layoutOrder = 1, transparency = self.props.transparency,
}), layoutOrder = 1,
}),
ConnectionDetails = e(ConnectionDetails, { ConnectionDetails = e(ConnectionDetails, {
projectName = self.state.projectName, projectName = self.state.projectName,
address = self.state.address, address = self.state.address,
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 2, layoutOrder = 2,
onDisconnect = self.props.onDisconnect, onDisconnect = self.props.onDisconnect,
}), }),
Layout = e("UIListLayout", { Info = e("TextLabel", {
VerticalAlignment = Enum.VerticalAlignment.Center, Text = self.props.patchInfo:map(function(info)
FillDirection = Enum.FillDirection.Vertical, return string.format(
SortOrder = Enum.SortOrder.LayoutOrder, "<i>Synced %d change%s %s</i>",
Padding = UDim.new(0, 10), info.changes,
}), info.changes == 1 and "" or "s",
timeSinceText(os.time() - info.timestamp)
)
end),
Font = Enum.Font.Gotham,
TextSize = 14,
TextWrapped = true,
RichText = true,
TextColor3 = theme.Header.VersionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
TextTransparency = self.props.transparency,
Padding = e("UIPadding", { Size = UDim2.new(1, 0, 0, 28),
PaddingLeft = UDim.new(0, 20),
PaddingRight = UDim.new(0, 20), LayoutOrder = 3,
}), BackgroundTransparency = 1,
}) }),
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)
end end
function ConnectedPage.getDerivedStateFromProps(props) function ConnectedPage.getDerivedStateFromProps(props)

View File

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Spinner = require(Plugin.App.Components.Spinner) local Spinner = require(Plugin.App.Components.Spinner)

View File

@@ -2,8 +2,9 @@ local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
@@ -56,8 +57,16 @@ function Error:render()
end, end,
}, { }, {
ErrorMessage = Theme.with(function(theme) ErrorMessage = Theme.with(function(theme)
return e("TextLabel", { return e("TextBox", {
[Roact.Event.InputBegan] = function(rbx, input)
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
rbx.SelectionStart = 0
rbx.CursorPosition = #rbx.Text+1
end,
Text = self.props.errorMessage, Text = self.props.errorMessage,
TextEditable = false,
Font = Enum.Font.Code, Font = Enum.Font.Code,
TextSize = 16, TextSize = 16,
TextColor3 = theme.ErrorColor, TextColor3 = theme.ErrorColor,
@@ -65,10 +74,9 @@ function Error:render()
TextYAlignment = Enum.TextYAlignment.Top, TextYAlignment = Enum.TextYAlignment.Top,
TextTransparency = self.props.transparency, TextTransparency = self.props.transparency,
TextWrapped = true, TextWrapped = true,
ClearTextOnFocus = false,
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1, BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
}) })
end), end),

View File

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Config = require(Plugin.Config) local Config = require(Plugin.Config)
@@ -24,7 +25,7 @@ local function AddressEntry(props)
layoutOrder = props.layoutOrder, layoutOrder = props.layoutOrder,
}, { }, {
Host = e("TextBox", { Host = e("TextBox", {
Text = "", Text = props.host or "",
Font = Enum.Font.Code, Font = Enum.Font.Code,
TextSize = 18, TextSize = 18,
TextColor3 = theme.AddressEntry.TextColor, TextColor3 = theme.AddressEntry.TextColor,
@@ -32,6 +33,7 @@ local function AddressEntry(props)
TextTransparency = props.transparency, TextTransparency = props.transparency,
PlaceholderText = Config.defaultHost, PlaceholderText = Config.defaultHost,
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor, PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
ClearTextOnFocus = false,
Size = UDim2.new(1, -(HOST_OFFSET + DIVIDER_WIDTH + PORT_WIDTH), 1, 0), Size = UDim2.new(1, -(HOST_OFFSET + DIVIDER_WIDTH + PORT_WIDTH), 1, 0),
Position = UDim2.new(0, HOST_OFFSET, 0, 0), Position = UDim2.new(0, HOST_OFFSET, 0, 0),
@@ -39,17 +41,22 @@ local function AddressEntry(props)
ClipsDescendants = true, ClipsDescendants = true,
BackgroundTransparency = 1, BackgroundTransparency = 1,
[Roact.Ref] = props.hostRef, [Roact.Change.Text] = function(object)
if props.onHostChange ~= nil then
props.onHostChange(object.Text)
end
end
}), }),
Port = e("TextBox", { Port = e("TextBox", {
Text = "", Text = props.port or "",
Font = Enum.Font.Code, Font = Enum.Font.Code,
TextSize = 18, TextSize = 18,
TextColor3 = theme.AddressEntry.TextColor, TextColor3 = theme.AddressEntry.TextColor,
TextTransparency = props.transparency, TextTransparency = props.transparency,
PlaceholderText = Config.defaultPort, PlaceholderText = Config.defaultPort,
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor, PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
ClearTextOnFocus = false,
Size = UDim2.new(0, PORT_WIDTH, 1, 0), Size = UDim2.new(0, PORT_WIDTH, 1, 0),
Position = UDim2.new(1, 0, 0, 0), Position = UDim2.new(1, 0, 0, 0),
@@ -58,12 +65,14 @@ local function AddressEntry(props)
ClipsDescendants = true, ClipsDescendants = true,
BackgroundTransparency = 1, BackgroundTransparency = 1,
[Roact.Ref] = props.portRef,
[Roact.Change.Text] = function(object) [Roact.Change.Text] = function(object)
local text = object.Text local text = object.Text
text = text:gsub("%D", "") text = text:gsub("%D", "")
object.Text = text object.Text = text
if props.onPortChange ~= nil then
props.onPortChange(text)
end
end, end,
}, { }, {
Divider = e("Frame", { Divider = e("Frame", {
@@ -80,11 +89,6 @@ end
local NotConnectedPage = Roact.Component:extend("NotConnectedPage") local NotConnectedPage = Roact.Component:extend("NotConnectedPage")
function NotConnectedPage:init()
self.hostRef = Roact.createRef()
self.portRef = Roact.createRef()
end
function NotConnectedPage:render() function NotConnectedPage:render()
return Roact.createFragment({ return Roact.createFragment({
Header = e(Header, { Header = e(Header, {
@@ -93,8 +97,10 @@ function NotConnectedPage:render()
}), }),
AddressEntry = e(AddressEntry, { AddressEntry = e(AddressEntry, {
hostRef = self.hostRef, host = self.props.host,
portRef = self.portRef, port = self.props.port,
onHostChange = self.props.onHostChange,
onPortChange = self.props.onPortChange,
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 2, layoutOrder = 2,
}), }),
@@ -117,15 +123,7 @@ function NotConnectedPage:render()
style = "Solid", style = "Solid",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = 2, layoutOrder = 2,
onClick = function() onClick = self.props.onConnect,
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", { Layout = e("UIListLayout", {

View File

@@ -1,230 +0,0 @@
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,150 @@
local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local Settings = require(Plugin.Settings)
local Theme = require(Plugin.App.Theme)
local Checkbox = require(Plugin.App.Components.Checkbox)
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 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))
self:setState({
setting = Settings:get(self.props.id),
})
self.changedCleanup = Settings:onChanged(self.props.id, function(value)
self:setState({
setting = value,
})
end)
end
function Setting:willUnmount()
self.changedCleanup()
end
function Setting:render()
return Theme.with(function(theme)
theme = theme.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 = self.state.setting,
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
return Setting

View File

@@ -0,0 +1,122 @@
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme)
local IconButton = require(Plugin.App.Components.IconButton)
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
local Setting = require(script.Setting)
local e = Roact.createElement
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 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,
}),
ShowNotifications = e(Setting, {
id = "showNotifications",
name = "Show Notifications",
description = "Popup notifications in viewport",
transparency = self.props.transparency,
layoutOrder = 2,
}),
PlaySounds = e(Setting, {
id = "playSounds",
name = "Play Sounds",
description = "Toggle sound effects",
transparency = self.props.transparency,
layoutOrder = 3,
}),
TwoWaySync = e(Setting, {
id = "twoWaySync",
name = "Two-Way Sync",
description = "EXPERIMENTAL! Editing files in Studio will sync them into the filesystem",
transparency = self.props.transparency,
layoutOrder = 4,
}),
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

@@ -16,9 +16,10 @@ local function getStudio()
end end
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Log = require(Rojo.Log) local Log = require(Packages.Log)
local strict = require(script.Parent.Parent.strict) local strict = require(script.Parent.Parent.strict)
@@ -34,7 +35,7 @@ end
local BRAND_COLOR = hexColor(0xE13835) local BRAND_COLOR = hexColor(0xE13835)
local lightTheme = strict("LightTheme", { local lightTheme = strict("LightTheme", {
BackgroundColor = hexColor(0xF0F0F0), BackgroundColor = hexColor(0xFFFFFF),
Button = { Button = {
Solid = { Solid = {
ActionFillColor = hexColor(0xFFFFFF), ActionFillColor = hexColor(0xFFFFFF),
@@ -67,7 +68,7 @@ local lightTheme = strict("LightTheme", {
BackgroundColor = BRAND_COLOR, BackgroundColor = BRAND_COLOR,
}, },
Inactive = { Inactive = {
IconColor = hexColor(0xCACACA), IconColor = hexColor(0xEEEEEE),
BorderColor = hexColor(0xAFAFAF), BorderColor = hexColor(0xAFAFAF),
}, },
}, },
@@ -77,11 +78,11 @@ local lightTheme = strict("LightTheme", {
}, },
BorderedContainer = { BorderedContainer = {
BorderColor = hexColor(0xCBCBCB), BorderColor = hexColor(0xCBCBCB),
BackgroundColor = hexColor(0xE0E0E0), BackgroundColor = hexColor(0xEEEEEE),
}, },
Spinner = { Spinner = {
ForegroundColor = BRAND_COLOR, ForegroundColor = BRAND_COLOR,
BackgroundColor = hexColor(0xE0E0E0), BackgroundColor = hexColor(0xEEEEEE),
}, },
ConnectionDetails = { ConnectionDetails = {
ProjectNameColor = hexColor(0x00000), ProjectNameColor = hexColor(0x00000),
@@ -103,12 +104,16 @@ local lightTheme = strict("LightTheme", {
LogoColor = BRAND_COLOR, LogoColor = BRAND_COLOR,
VersionColor = hexColor(0x727272), VersionColor = hexColor(0x727272),
}, },
Notification = {
InfoColor = hexColor(0x00000),
CloseColor = BRAND_COLOR,
},
ErrorColor = hexColor(0x000000), ErrorColor = hexColor(0x000000),
ScrollBarColor = hexColor(0x000000), ScrollBarColor = hexColor(0x000000),
}) })
local darkTheme = strict("DarkTheme", { local darkTheme = strict("DarkTheme", {
BackgroundColor = hexColor(0x272727), BackgroundColor = hexColor(0x2E2E2E),
Button = { Button = {
Solid = { Solid = {
ActionFillColor = hexColor(0xFFFFFF), ActionFillColor = hexColor(0xFFFFFF),
@@ -147,15 +152,15 @@ local darkTheme = strict("DarkTheme", {
}, },
AddressEntry = { AddressEntry = {
TextColor = hexColor(0xFFFFFF), TextColor = hexColor(0xFFFFFF),
PlaceholderColor = hexColor(0x717171) PlaceholderColor = hexColor(0x8B8B8B)
}, },
BorderedContainer = { BorderedContainer = {
BorderColor = hexColor(0x535353), BorderColor = hexColor(0x535353),
BackgroundColor = hexColor(0x323232), BackgroundColor = hexColor(0x2B2B2B),
}, },
Spinner = { Spinner = {
ForegroundColor = BRAND_COLOR, ForegroundColor = BRAND_COLOR,
BackgroundColor = hexColor(0x323232), BackgroundColor = hexColor(0x2B2B2B),
}, },
ConnectionDetails = { ConnectionDetails = {
ProjectNameColor = hexColor(0xFFFFFF), ProjectNameColor = hexColor(0xFFFFFF),
@@ -177,6 +182,10 @@ local darkTheme = strict("DarkTheme", {
LogoColor = BRAND_COLOR, LogoColor = BRAND_COLOR,
VersionColor = hexColor(0xD3D3D3) VersionColor = hexColor(0xD3D3D3)
}, },
Notification = {
InfoColor = hexColor(0xFFFFFF),
CloseColor = hexColor(0xFFFFFF),
},
ErrorColor = hexColor(0xFFFFFF), ErrorColor = hexColor(0xFFFFFF),
ScrollBarColor = hexColor(0xFFFFFF), ScrollBarColor = hexColor(0xFFFFFF),
}) })

View File

@@ -1,7 +1,8 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Log = require(Rojo.Log) local Log = require(Packages.Log)
local LERP_DATA_TYPES = { local LERP_DATA_TYPES = {
Color3 = true, Color3 = true,

View File

@@ -1,21 +1,28 @@
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Rojo.Roact) local Roact = require(Packages.Roact)
local Log = require(Rojo.Log) local Log = require(Packages.Log)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local Version = require(Plugin.Version) local Version = require(Plugin.Version)
local Config = require(Plugin.Config) local Config = require(Plugin.Config)
local Settings = require(Plugin.Settings)
local strict = require(Plugin.strict) local strict = require(Plugin.strict)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)
local ServeSession = require(Plugin.ServeSession) local ServeSession = require(Plugin.ServeSession)
local ApiContext = require(Plugin.ApiContext) local ApiContext = require(Plugin.ApiContext)
local preloadAssets = require(Plugin.preloadAssets) local preloadAssets = require(Plugin.preloadAssets)
local soundPlayer = require(Plugin.soundPlayer)
local Theme = require(script.Theme) local Theme = require(script.Theme)
local PluginSettings = require(script.PluginSettings)
local Page = require(script.Page) local Page = require(script.Page)
local Notifications = require(script.Notifications)
local StudioPluginAction = require(script.Components.Studio.StudioPluginAction)
local StudioToolbar = require(script.Components.Studio.StudioToolbar) local StudioToolbar = require(script.Components.Studio.StudioToolbar)
local StudioToggleButton = require(script.Components.Studio.StudioToggleButton) local StudioToggleButton = require(script.Components.Studio.StudioToggleButton)
local StudioPluginGui = require(script.Components.Studio.StudioPluginGui) local StudioPluginGui = require(script.Components.Studio.StudioPluginGui)
@@ -37,13 +44,123 @@ local App = Roact.Component:extend("App")
function App:init() function App:init()
preloadAssets() preloadAssets()
self.host, self.setHost = Roact.createBinding("")
self.port, self.setPort = Roact.createBinding("")
self.patchInfo, self.setPatchInfo = Roact.createBinding({
changes = 0,
timestamp = os.time(),
})
self:setState({ self:setState({
appStatus = AppStatus.NotConnected, appStatus = AppStatus.NotConnected,
guiEnabled = false, guiEnabled = false,
notifications = {},
toolbarIcon = Assets.Images.PluginButton,
}) })
end end
function App:startSession(host, port, sessionOptions) function App:addNotification(text: string, timeout: number?)
if not Settings:get("showNotifications") then
return
end
local notifications = table.clone(self.state.notifications)
table.insert(notifications, {
text = text,
timestamp = DateTime.now().UnixTimestampMillis,
timeout = timeout or 3,
})
self:setState({
notifications = notifications,
})
end
function App:closeNotification(index: number)
local notifications = table.clone(self.state.notifications)
table.remove(notifications, index)
self:setState({
notifications = notifications,
})
end
function App:getHostAndPort()
local host = self.host:getValue()
local port = self.port:getValue()
local host = if #host > 0 then host else Config.defaultHost
local port = if #port > 0 then port else Config.defaultPort
return host, port
end
function App:claimSyncLock()
if #Players:GetPlayers() == 0 then
Log.trace("Skipping sync lock because this isn't in Team Create")
return true
end
local lock = ServerStorage:FindFirstChild("__Rojo_SessionLock")
if not lock then
lock = Instance.new("ObjectValue")
lock.Name = "__Rojo_SessionLock"
lock.Archivable = false
lock.Value = Players.LocalPlayer
lock.Parent = ServerStorage
Log.trace("Created and claimed sync lock")
return true
end
if lock.Value and lock.Value ~= Players.LocalPlayer and lock.Value.Parent then
Log.trace("Found existing sync lock owned by {}", lock.Value)
return false, lock.Value
end
lock.Value = Players.LocalPlayer
Log.trace("Claimed existing sync lock")
return true
end
function App:releaseSyncLock()
local lock = ServerStorage:FindFirstChild("__Rojo_SessionLock")
if not lock then
Log.trace("No sync lock found, assumed released")
return
end
if lock.Value == Players.LocalPlayer then
lock.Value = nil
Log.trace("Released sync lock")
return
end
Log.trace("Could not relase sync lock because it is owned by {}", lock.Value)
end
function App:startSession()
local claimedLock, priorOwner = self:claimSyncLock()
if not claimedLock then
local msg = string.format("Could not sync because user '%s' is already syncing", tostring(priorOwner))
Log.warn(msg)
self:addNotification(msg, 10)
self:setState({
appStatus = AppStatus.Error,
errorMessage = msg,
toolbarIcon = Assets.Images.PluginButtonWarning,
})
return
end
local host, port = self:getHostAndPort()
local sessionOptions = {
openScriptsExternally = Settings:get("openScriptsExternally"),
twoWaySync = Settings:get("twoWaySync"),
}
local baseUrl = ("http://%s:%s"):format(host, port) local baseUrl = ("http://%s:%s"):format(host, port)
local apiContext = ApiContext.new(baseUrl) local apiContext = ApiContext.new(baseUrl)
@@ -53,20 +170,53 @@ function App:startSession(host, port, sessionOptions)
twoWaySync = sessionOptions.twoWaySync, twoWaySync = sessionOptions.twoWaySync,
}) })
serveSession:onPatchApplied(function(patch, unapplied)
local now = os.time()
local changes = 0
for _, set in patch do
for _ in set do
changes += 1
end
end
for _, set in unapplied do
for _ in set do
changes -= 1
end
end
if changes == 0 then return end
local old = self.patchInfo:getValue()
if now - old.timestamp < 2 then
changes += old.changes
end
self.setPatchInfo({
changes = changes,
timestamp = now,
})
end)
serveSession:onStatusChanged(function(status, details) serveSession:onStatusChanged(function(status, details)
if status == ServeSession.Status.Connecting then if status == ServeSession.Status.Connecting then
self:setState({ self:setState({
appStatus = AppStatus.Connecting, appStatus = AppStatus.Connecting,
toolbarIcon = Assets.Images.PluginButton,
}) })
self:addNotification("Connecting to session...")
elseif status == ServeSession.Status.Connected then elseif status == ServeSession.Status.Connected then
local address = ("%s:%s"):format(host, port) local address = ("%s:%s"):format(host, port)
self:setState({ self:setState({
appStatus = AppStatus.Connected, appStatus = AppStatus.Connected,
projectName = details, projectName = details,
address = address, address = address,
toolbarIcon = Assets.Images.PluginButtonConnected,
}) })
self:addNotification(string.format("Connected to session '%s' at %s.", details, address), 5)
elseif status == ServeSession.Status.Disconnected then elseif status == ServeSession.Status.Disconnected then
self.serveSession = nil self.serveSession = nil
self:releaseSyncLock()
-- Details being present indicates that this -- Details being present indicates that this
-- disconnection was from an error. -- disconnection was from an error.
@@ -76,11 +226,15 @@ function App:startSession(host, port, sessionOptions)
self:setState({ self:setState({
appStatus = AppStatus.Error, appStatus = AppStatus.Error,
errorMessage = tostring(details), errorMessage = tostring(details),
toolbarIcon = Assets.Images.PluginButtonWarning,
}) })
self:addNotification(tostring(details), 10)
else else
self:setState({ self:setState({
appStatus = AppStatus.NotConnected, appStatus = AppStatus.NotConnected,
toolbarIcon = Assets.Images.PluginButton,
}) })
self:addNotification("Disconnected from session.")
end end
end end
end) end)
@@ -88,6 +242,32 @@ function App:startSession(host, port, sessionOptions)
serveSession:start() serveSession:start()
self.serveSession = serveSession self.serveSession = serveSession
task.defer(function()
while self.serveSession == serveSession do
-- Trigger rerender to update timestamp text
local patchInfo = table.clone(self.patchInfo:getValue())
self.setPatchInfo(patchInfo)
local elapsed = os.time() - patchInfo.timestamp
task.wait(elapsed < 60 and 1 or elapsed/5)
end
end)
end
function App:endSession()
if self.serveSession == nil then
return
end
Log.trace("Disconnecting session")
self.serveSession:stop()
self.serveSession = nil
self:setState({
appStatus = AppStatus.NotConnected,
})
Log.trace("Session terminated by user")
end end
function App:render() function App:render()
@@ -108,119 +288,178 @@ function App:render()
value = self.props.plugin, value = self.props.plugin,
}, { }, {
e(Theme.StudioProvider, nil, { e(Theme.StudioProvider, nil, {
e(PluginSettings.StudioProvider, { gui = e(StudioPluginGui, {
plugin = self.props.plugin, 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,
}, { }, {
gui = e(StudioPluginGui, { NotConnectedPage = createPageElement(AppStatus.NotConnected, {
id = pluginName, host = self.host,
title = pluginName, onHostChange = self.setHost,
active = self.state.guiEnabled, port = self.port,
onPortChange = self.setPort,
initDockState = Enum.InitialDockState.Right, onConnect = function()
initEnabled = false, self:startSession()
overridePreviousState = false, end,
floatingSize = Vector2.new(300, 200),
minimumSize = Vector2.new(300, 200),
zIndexBehavior = Enum.ZIndexBehavior.Sibling, onNavigateSettings = function()
onInitialState = function(initialState)
self:setState({ self:setState({
guiEnabled = initialState, appStatus = AppStatus.Settings,
}) })
end, end,
}),
Connecting = createPageElement(AppStatus.Connecting),
Connected = createPageElement(AppStatus.Connected, {
projectName = self.state.projectName,
address = self.state.address,
patchInfo = self.patchInfo,
onDisconnect = function()
self:endSession()
end,
}),
Settings = createPageElement(AppStatus.Settings, {
onBack = function()
self:setState({
appStatus = AppStatus.NotConnected,
})
end,
}),
Error = createPageElement(AppStatus.Error, {
errorMessage = self.state.errorMessage,
onClose = function() onClose = function()
self:setState({ self:setState({
guiEnabled = false, appStatus = AppStatus.NotConnected,
toolbarIcon = Assets.Images.PluginButton,
}) })
end, 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, { Background = Theme.with(function(theme)
name = pluginName, return e("Frame", {
}, { Size = UDim2.new(1, 0, 1, 0),
button = e(StudioToggleButton, { BackgroundColor3 = theme.BackgroundColor,
name = "Rojo", ZIndex = 0,
tooltip = "Show or hide the Rojo panel", BorderSizePixel = 0,
icon = Assets.Images.PluginButton,
active = self.state.guiEnabled,
enabled = true,
onClick = function()
self:setState(function(state)
return {
guiEnabled = not state.guiEnabled,
}
end)
end,
}) })
end),
}),
RojoNotifications = e("ScreenGui", {}, {
layout = e("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder,
HorizontalAlignment = Enum.HorizontalAlignment.Right,
VerticalAlignment = Enum.VerticalAlignment.Bottom,
Padding = UDim.new(0, 5),
}), }),
padding = e("UIPadding", {
PaddingTop = UDim.new(0, 5);
PaddingBottom = UDim.new(0, 5);
PaddingLeft = UDim.new(0, 5);
PaddingRight = UDim.new(0, 5);
}),
notifs = e(Notifications, {
soundPlayer = self.props.soundPlayer,
notifications = self.state.notifications,
onClose = function(index)
self:closeNotification(index)
end,
}),
}),
toggleAction = e(StudioPluginAction, {
name = "RojoConnection",
title = "Rojo: Connect/Disconnect",
description = "Toggles the server for a Rojo sync session",
icon = Assets.Images.PluginButton,
bindable = true,
onTriggered = function()
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
self:startSession()
elseif self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
self:endSession()
end
end,
}),
connectAction = e(StudioPluginAction, {
name = "RojoConnect",
title = "Rojo: Connect",
description = "Connects the server for a Rojo sync session",
icon = Assets.Images.PluginButton,
bindable = true,
onTriggered = function()
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
self:startSession()
end
end,
}),
disconnectAction = e(StudioPluginAction, {
name = "RojoDisconnect",
title = "Rojo: Disconnect",
description = "Disconnects the server for a Rojo sync session",
icon = Assets.Images.PluginButton,
bindable = true,
onTriggered = function()
if self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
self:endSession()
end
end,
}),
toolbar = e(StudioToolbar, {
name = pluginName,
}, {
button = e(StudioToggleButton, {
name = "Rojo",
tooltip = "Show or hide the Rojo panel",
icon = self.state.toolbarIcon,
active = self.state.guiEnabled,
enabled = true,
onClick = function()
self:setState(function(state)
return {
guiEnabled = not state.guiEnabled,
}
end)
end,
})
}), }),
}), }),
}) })
end end
return App return function(props)
local mergedProps = Dictionary.merge(props, {
soundPlayer = soundPlayer.new(Settings),
})
return e(App, mergedProps)
end

View File

@@ -18,6 +18,8 @@ local Assets = {
Images = { Images = {
Logo = "rbxassetid://5990772764", Logo = "rbxassetid://5990772764",
PluginButton = "rbxassetid://3405341609", PluginButton = "rbxassetid://3405341609",
PluginButtonConnected = "rbxassetid://9529783993",
PluginButtonWarning = "rbxassetid://9529784530",
Icons = { Icons = {
Close = "rbxassetid://6012985953", Close = "rbxassetid://6012985953",
Back = "rbxassetid://6017213752", Back = "rbxassetid://6017213752",
@@ -43,6 +45,9 @@ local Assets = {
[500] = "rbxassetid://2609138523" [500] = "rbxassetid://2609138523"
}, },
}, },
Sounds = {
Notification = "rbxassetid://203785492",
},
StartSession = "", StartSession = "",
SessionActive = "", SessionActive = "",
Configure = "", Configure = "",

View File

@@ -0,0 +1,41 @@
--[[
Take an InstanceMap and a dictionary mapping instances to sets of property
names. Populate a patch with the encoded values of all the given properties
on all the given instances (or, if any changes set Parent to nil, removals
of instances) and return the patch.
]]
local Packages = script.Parent.Parent.Parent.Packages
local Log = require(Packages.Log)
local PatchSet = require(script.Parent.Parent.PatchSet)
local encodePatchUpdate = require(script.Parent.encodePatchUpdate)
return function(instanceMap, propertyChanges)
local patch = PatchSet.newEmpty()
for instance, properties in pairs(propertyChanges) do
local instanceId = instanceMap.fromInstances[instance]
if instanceId == nil then
Log.warn("Ignoring change for instance {:?} as it is unknown to Rojo", instance)
continue
end
if properties.Parent then
if instance.Parent == nil then
table.insert(patch.removed, instanceId)
else
Log.warn("Cannot sync non-nil Parent property changes yet")
end
else
local update = encodePatchUpdate(instance, instanceId, properties)
table.insert(patch.updated, update)
end
propertyChanges[instance] = nil
end
return patch
end

View File

@@ -0,0 +1,74 @@
return function()
local PatchSet = require(script.Parent.Parent.PatchSet)
local InstanceMap = require(script.Parent.Parent.InstanceMap)
local createPatchSet = require(script.Parent.createPatchSet)
it("should return a patch", function()
local patch = createPatchSet(InstanceMap.new(), {})
assert(PatchSet.validate(patch))
end)
it("should contain updates for every instance with property changes", function()
local instanceMap = InstanceMap.new()
local part1 = Instance.new("Part")
instanceMap:insert("PART_1", part1)
local part2 = Instance.new("Part")
instanceMap:insert("PART_2", part2)
local changes = {
[part1] = {
Position = true,
Size = true,
Color = true,
},
[part2] = {
CFrame = true,
Velocity = true,
Transparency = true,
},
}
local patch = createPatchSet(instanceMap, changes)
expect(#patch.updated).to.equal(2)
end)
it("should not contain any updates for removed instances", function()
local instanceMap = InstanceMap.new()
local part1 = Instance.new("Part")
instanceMap:insert("PART_1", part1)
local changes = {
[part1] = {
Parent = true,
Position = true,
Size = true,
},
}
local patch = createPatchSet(instanceMap, changes)
expect(#patch.removed).to.equal(1)
expect(#patch.updated).to.equal(0)
end)
it("should remove instances from the property change table", function()
local instanceMap = InstanceMap.new()
local part1 = Instance.new("Part")
instanceMap:insert("PART_1", part1)
local changes = {
[part1] = {},
}
createPatchSet(instanceMap, changes)
expect(next(changes)).to.equal(nil)
end)
end

View File

@@ -0,0 +1,40 @@
local Packages = script.Parent.Parent.Parent.Packages
local Log = require(Packages.Log)
local RbxDom = require(Packages.RbxDom)
local encodeProperty = require(script.Parent.encodeProperty)
return function(instance, instanceId, properties)
local update = {
id = instanceId,
changedProperties = {},
}
for propertyName in pairs(properties) do
if propertyName == "Name" then
update.changedName = instance.Name
else
local descriptor = RbxDom.findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
if not descriptor then
Log.debug("Could not sync back property {:?}.{}", instance, propertyName)
continue
end
local encodeSuccess, encodeResult = encodeProperty(instance, propertyName, descriptor)
if not encodeSuccess then
Log.debug("Could not sync back property {:?}.{}: {}", instance, propertyName, encodeResult)
continue
end
update.changedProperties[propertyName] = encodeResult
end
end
if next(update.changedProperties) == nil and update.changedName == nil then
return nil
end
return update
end

View File

@@ -0,0 +1,62 @@
return function()
local encodePatchUpdate = require(script.Parent.encodePatchUpdate)
it("should return an update when there are property changes", function()
local part = Instance.new("Part")
local properties = {
CFrame = true,
Color = true,
}
local update = encodePatchUpdate(part, "PART", properties)
expect(update.id).to.equal("PART")
expect(update.changedProperties.CFrame).to.be.ok()
expect(update.changedProperties.Color).to.be.ok()
end)
it("should return nil when there are no property changes", function()
local part = Instance.new("Part")
local properties = {
NonExistentProperty = true,
}
local update = encodePatchUpdate(part, "PART", properties)
expect(update).to.equal(nil)
end)
it("should set changedName in the update when the instance's Name changes", function()
local part = Instance.new("Part")
local properties = {
Name = true,
}
part.Name = "We'reGettingToTheCoolPart"
local update = encodePatchUpdate(part, "PART", properties)
expect(update.changedName).to.equal("We'reGettingToTheCoolPart")
end)
it("should correctly encode property values", function()
local part = Instance.new("Part")
local properties = {
Position = true,
Color = true,
}
part.Position = Vector3.new(0, 100, 0)
part.Color = Color3.new(0.8, 0.2, 0.9)
local update = encodePatchUpdate(part, "PART", properties)
local position = update.changedProperties.Position
local color = update.changedProperties.Color
expect(position.Vector3[1]).to.equal(0)
expect(position.Vector3[2]).to.equal(100)
expect(position.Vector3[3]).to.equal(0)
expect(color.Color3[1]).to.be.near(0.8, 0.01)
expect(color.Color3[2]).to.be.near(0.2, 0.01)
expect(color.Color3[3]).to.be.near(0.9, 0.01)
end)
end

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