Compare commits

..

1 Commits

Author SHA1 Message Date
Lucien Greathouse
b96a236333 Refactor project file into its own crate 2022-05-26 05:00:57 -04:00
134 changed files with 2549 additions and 6036 deletions

View File

@@ -16,10 +16,12 @@ jobs:
strategy: strategy:
matrix: matrix:
rust_version: [stable, 1.58.1] rust_version: [stable, 1.55.0]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
submodules: true
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -28,19 +30,6 @@ jobs:
override: true override: true
profile: minimal profile: minimal
- name: Setup Aftman
uses: ok-nick/setup-aftman@v0.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
trust-check: false
version: 'v0.2.6'
- name: Install packages
run: |
cd plugin
wally install
cd ..
- name: Build - name: Build
run: cargo build --locked --verbose run: cargo build --locked --verbose
@@ -53,6 +42,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
submodules: true
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -61,19 +52,6 @@ jobs:
override: true override: true
components: rustfmt, clippy components: rustfmt, clippy
- name: Setup Aftman
uses: ok-nick/setup-aftman@v0.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
trust-check: false
version: 'v0.2.6'
- name: Install packages
run: |
cd plugin
wally install
cd ..
- name: Rustfmt - name: Rustfmt
run: cargo fmt -- --check run: cargo fmt -- --check

View File

@@ -28,19 +28,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
submodules: true
- name: Setup Aftman - name: Setup Foreman
uses: ok-nick/setup-aftman@v0.1.0 uses: Roblox/setup-foreman@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
trust-check: false
version: 'v0.2.6'
- name: Install packages
run: |
cd plugin
wally install
cd ..
- name: Build Plugin - name: Build Plugin
run: rojo build plugin --output Rojo.rbxm run: rojo build plugin --output Rojo.rbxm
@@ -67,21 +61,25 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
# https://doc.rust-lang.org/rustc/platform-support.html # https://doc.rust-lang.org/rustc/platform-support.html
#
# FIXME: After the Rojo VS Code extension updates, add architecture
# names to each of these releases. We'll rename win64 to windows and add
# -x86_64 to each release.
include: include:
- host: linux - host: linux
os: ubuntu-20.04 os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
label: linux-x86_64 label: linux
- host: windows - host: windows
os: windows-latest os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
label: windows-x86_64 label: win64
- host: macos - host: macos
os: macos-latest os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
label: macos-x86_64 label: macos
- host: macos - host: macos
os: macos-latest os: macos-latest
@@ -94,6 +92,8 @@ jobs:
BIN: rojo BIN: rojo
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
submodules: true
- name: Get Version from Tag - name: Get Version from Tag
shell: bash shell: bash
@@ -110,20 +110,6 @@ jobs:
override: true override: true
profile: minimal profile: minimal
- name: Setup Aftman
uses: ok-nick/setup-aftman@v0.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
trust-check: false
version: 'v0.2.6'
- name: Install packages
run: |
cd plugin
wally install
cd ..
shell: bash
- name: Build Release - name: Build Release
run: cargo build --release --locked --verbose run: cargo build --release --locked --verbose
env: env:

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 Normal file
View File

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

58
.luacheckrc Normal file
View File

@@ -0,0 +1,58 @@
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,40 +1,7 @@
# 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. * 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 ## [7.1.1] - May 26, 2022
* Fixed sourcemap command not stripping paths correctly ([#544]) * Fixed sourcemap command not stripping paths correctly ([#544])

565
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
[package] [package]
name = "rojo" name = "rojo"
version = "7.2.1" version = "7.1.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"
@@ -9,7 +8,7 @@ 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 = "2021" edition = "2018"
build = "build.rs" build = "build.rs"
exclude = [ exclude = [
@@ -28,8 +27,6 @@ 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 = ["crates/*"] members = ["crates/*"]
@@ -42,6 +39,7 @@ name = "build"
harness = false harness = false
[dependencies] [dependencies]
rojo-project = { path = "crates/rojo-project" }
memofs = { version = "0.2.0", path = "crates/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
@@ -51,8 +49,8 @@ memofs = { version = "0.2.0", path = "crates/memofs" }
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" } # rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
# rbx_xml = { path = "../rbx-dom/rbx_xml" } # rbx_xml = { path = "../rbx-dom/rbx_xml" }
rbx_binary = "0.6.5" rbx_binary = "0.6.4"
rbx_dom_weak = "2.4.0" rbx_dom_weak = "2.3.0"
rbx_reflection = "4.2.0" rbx_reflection = "4.2.0"
rbx_reflection_database = "0.2.2" rbx_reflection_database = "0.2.2"
rbx_xml = "0.12.3" rbx_xml = "0.12.3"
@@ -73,7 +71,7 @@ log = "0.4.14"
maplit = "1.0.2" maplit = "1.0.2"
notify = "4.0.17" notify = "4.0.17"
opener = "0.5.0" opener = "0.5.0"
reqwest = { version = "0.11.10", features = ["blocking", "json", "native-tls-vendored"] } reqwest = { version = "0.11.10", features = ["blocking", "json"] }
ritz = "0.1.0" ritz = "0.1.0"
roblox_install = "1.0.0" roblox_install = "1.0.0"
serde = { version = "1.0.130", features = ["derive", "rc"] } serde = { version = "1.0.130", features = ["derive", "rc"] }
@@ -83,8 +81,6 @@ thiserror = "1.0.30"
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] } tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
uuid = { version = "1.0.0", features = ["v4", "serde"] } uuid = { version = "1.0.0", features = ["v4", "serde"] }
clap = { version = "3.1.18", features = ["derive"] } clap = { version = "3.1.18", features = ["derive"] }
profiling = "1.0.6"
tracy-client = { version = "0.13.2", optional = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.10.1" winreg = "0.10.1"

View File

@@ -40,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.58.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.46.0 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.

View File

@@ -1,5 +0,0 @@
[tools]
wally = "UpliftGames/wally@0.3.1"
rojo = "rojo-rbx/rojo@7.2.1"
selene = "Kampfkarren/selene@0.20.0"
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"

Binary file not shown.

View File

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

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::BuildCommand; use librojo::cli::{build, 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)| options.run().unwrap(), |(_dir, options)| build(options).unwrap(),
BatchSize::SmallInput, BatchSize::SmallInput,
) )
}); });

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") || file_name.ends_with(".spec.luau") { if file_name.ends_with(".spec.lua") {
continue; continue;
} }
@@ -43,6 +43,8 @@ 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"))?,
@@ -50,7 +52,20 @@ 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"))?,
"Packages" => snapshot_from_fs_path(&plugin_root.join("Packages"))?, "modules" => VfsSnapshot::dir(hashmap! {
"roact" => VfsSnapshot::dir(hashmap! {
"src" => snapshot_from_fs_path(&plugin_modules.join("roact").join("src"))?
}),
"promise" => VfsSnapshot::dir(hashmap! {
"lib" => snapshot_from_fs_path(&plugin_modules.join("promise").join("lib"))?
}),
"t" => VfsSnapshot::dir(hashmap! {
"lib" => snapshot_from_fs_path(&plugin_modules.join("t").join("lib"))?
}),
"flipper" => VfsSnapshot::dir(hashmap! {
"src" => snapshot_from_fs_path(&plugin_modules.join("flipper").join("src"))?
}),
}),
}); });
let out_path = Path::new(&out_dir).join("plugin.bincode"); let out_path = Path::new(&out_dir).join("plugin.bincode");

View File

@@ -0,0 +1,16 @@
[package]
name = "rojo-project"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.57"
globset = { version = "0.4.8", features = ["serde1"] }
log = "0.4.17"
rbx_dom_weak = "2.3.0"
rbx_reflection = "4.2.0"
rbx_reflection_database = "0.2.4"
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.81"

View File

@@ -0,0 +1,4 @@
# rojo-project
Project file format crate for [Rojo].
[Rojo]: https://rojo.space

View File

@@ -1,6 +1,3 @@
//! Wrapper around globset's Glob type that has better serialization
//! characteristics by coupling Glob and GlobMatcher into a single type.
use std::path::Path; use std::path::Path;
use globset::{Glob as InnerGlob, GlobMatcher}; use globset::{Glob as InnerGlob, GlobMatcher};
@@ -8,6 +5,8 @@ use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
pub use globset::Error; pub use globset::Error;
/// Wrapper around globset's Glob type that has better serialization
/// characteristics by coupling Glob and GlobMatcher into a single type.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Glob { pub struct Glob {
inner: InnerGlob, inner: InnerGlob,

View File

@@ -0,0 +1,7 @@
pub mod glob;
mod path_serializer;
mod project;
mod resolution;
pub use project::{OptionalPathNode, PathNode, Project, ProjectNode};
pub use resolution::{AmbiguousValue, UnresolvedValue};

View File

@@ -0,0 +1,21 @@
//! Path serializer is used to serialize absolute paths in a cross-platform way,
//! by replacing all directory separators with /.
use std::path::Path;
use serde::Serializer;
pub fn serialize_absolute<S, T>(path: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<Path>,
{
let as_str = path
.as_ref()
.as_os_str()
.to_str()
.expect("Invalid Unicode in file path, cannot serialize");
let replaced = as_str.replace("\\", "/");
serializer.serialize_str(&replaced)
}

View File

@@ -0,0 +1,363 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fs;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use anyhow::Context;
use serde::{Deserialize, Serialize};
use crate::glob::Glob;
use crate::resolution::UnresolvedValue;
static PROJECT_FILENAME: &str = "default.project.json";
/// Contains all of the configuration for a Rojo-managed project.
///
/// Rojo project files are stored in `.project.json` files.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct Project {
/// The name of the top-level instance described by the project.
pub name: String,
/// The tree of instances described by this project. Projects always
/// describe at least one instance.
pub tree: ProjectNode,
/// If specified, sets the default port that `rojo serve` should use when
/// using this project for live sync.
///
/// Can be overriden with the `--port` flag.
#[serde(skip_serializing_if = "Option::is_none")]
pub serve_port: Option<u16>,
/// If specified, sets the default IP address that `rojo serve` should use
/// when using this project for live sync.
///
/// Can be overridden with the `--address` flag.
#[serde(skip_serializing_if = "Option::is_none")]
pub serve_address: Option<IpAddr>,
/// If specified, contains the set of place IDs that this project is
/// compatible with when doing live sync.
///
/// This setting is intended to help prevent syncing a Rojo project into the
/// wrong Roblox place.
#[serde(skip_serializing_if = "Option::is_none")]
pub serve_place_ids: Option<HashSet<u64>>,
/// If specified, sets the current place's place ID when connecting to the
/// Rojo server from Roblox Studio.
#[serde(skip_serializing_if = "Option::is_none")]
pub place_id: Option<u64>,
/// If specified, sets the current place's game ID when connecting to the
/// Rojo server from Roblox Studio.
#[serde(skip_serializing_if = "Option::is_none")]
pub game_id: Option<u64>,
/// A list of globs, relative to the folder the project file is in, that
/// match files that should be excluded if Rojo encounters them.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub glob_ignore_paths: Vec<Glob>,
/// The path to the file that this project came from. Relative paths in the
/// project should be considered relative to the parent of this field, also
/// given by `Project::folder_location`.
#[serde(skip)]
pub file_location: PathBuf,
}
impl Project {
/// Tells whether the given path describes a Rojo project.
pub fn is_project_file(path: &Path) -> bool {
path.file_name()
.and_then(|name| name.to_str())
.map(|name| name.ends_with(".project.json"))
.unwrap_or(false)
}
/// Loads a project file from a slice and a path that indicates where the
/// project should resolve paths relative to.
pub fn load_from_slice(contents: &[u8], project_file_location: &Path) -> anyhow::Result<Self> {
let mut project: Self = serde_json::from_slice(&contents).with_context(|| {
format!(
"Error parsing Rojo project at {}",
project_file_location.display()
)
})?;
project.file_location = project_file_location.to_path_buf();
project.check_compatibility();
Ok(project)
}
/// Fuzzy-find a Rojo project and load it.
pub fn load_fuzzy(fuzzy_project_location: &Path) -> anyhow::Result<Option<Self>> {
if let Some(project_path) = Self::locate(fuzzy_project_location) {
let project = Self::load_exact(&project_path)?;
Ok(Some(project))
} else {
Ok(None)
}
}
/// Gives the path that all project file paths should resolve relative to.
pub fn folder_location(&self) -> &Path {
self.file_location.parent().unwrap()
}
/// Attempt to locate a project represented by the given path.
///
/// This will find a project if the path refers to a `.project.json` file,
/// or is a folder that contains a `default.project.json` file.
fn locate(path: &Path) -> Option<PathBuf> {
let meta = fs::metadata(path).ok()?;
if meta.is_file() {
if Project::is_project_file(path) {
Some(path.to_path_buf())
} else {
None
}
} else {
let child_path = path.join(PROJECT_FILENAME);
let child_meta = fs::metadata(&child_path).ok()?;
if child_meta.is_file() {
Some(child_path)
} else {
// This is a folder with the same name as a Rojo default project
// file.
//
// That's pretty weird, but we can roll with it.
None
}
}
}
fn load_exact(project_file_location: &Path) -> anyhow::Result<Self> {
let contents = fs::read_to_string(project_file_location)?;
let mut project: Project = serde_json::from_str(&contents).with_context(|| {
format!(
"Error parsing Rojo project at {}",
project_file_location.display()
)
})?;
project.file_location = project_file_location.to_path_buf();
project.check_compatibility();
Ok(project)
}
/// Checks if there are any compatibility issues with this project file and
/// warns the user if there are any.
fn check_compatibility(&self) {
self.tree.validate_reserved_names();
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct OptionalPathNode {
#[serde(serialize_with = "crate::path_serializer::serialize_absolute")]
pub optional: PathBuf,
}
impl OptionalPathNode {
pub fn new(optional: PathBuf) -> Self {
OptionalPathNode { optional }
}
}
/// Describes a path that is either optional or required
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PathNode {
Required(#[serde(serialize_with = "crate::path_serializer::serialize_absolute")] PathBuf),
Optional(OptionalPathNode),
}
impl PathNode {
pub fn path(&self) -> &Path {
match self {
PathNode::Required(pathbuf) => &pathbuf,
PathNode::Optional(OptionalPathNode { optional }) => &optional,
}
}
}
/// Describes an instance and its descendants in a project.
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct ProjectNode {
/// If set, defines the ClassName of the described instance.
///
/// `$className` MUST be set if `$path` is not set.
///
/// `$className` CANNOT be set if `$path` is set and the instance described
/// by that path has a ClassName other than Folder.
#[serde(rename = "$className", skip_serializing_if = "Option::is_none")]
pub class_name: Option<String>,
/// Contains all of the children of the described instance.
#[serde(flatten)]
pub children: BTreeMap<String, ProjectNode>,
/// The properties that will be assigned to the resulting instance.
#[serde(
rename = "$properties",
default,
skip_serializing_if = "HashMap::is_empty"
)]
pub properties: HashMap<String, UnresolvedValue>,
/// Defines the behavior when Rojo encounters unknown instances in Roblox
/// Studio during live sync. `$ignoreUnknownInstances` should be considered
/// a large hammer and used with care.
///
/// If set to `true`, those instances will be left alone. This may cause
/// issues when files that turn into instances are removed while Rojo is not
/// running.
///
/// If set to `false`, Rojo will destroy any instances it does not
/// recognize.
///
/// If unset, its default value depends on other settings:
/// - If `$path` is not set, defaults to `true`
/// - If `$path` is set, defaults to `false`
#[serde(
rename = "$ignoreUnknownInstances",
skip_serializing_if = "Option::is_none"
)]
pub ignore_unknown_instances: Option<bool>,
/// Defines that this instance should come from the given file path. This
/// path can point to any file type supported by Rojo, including Lua files
/// (`.lua`), Roblox models (`.rbxm`, `.rbxmx`), and localization table
/// spreadsheets (`.csv`).
#[serde(rename = "$path", skip_serializing_if = "Option::is_none")]
pub path: Option<PathNode>,
}
impl ProjectNode {
fn validate_reserved_names(&self) {
for (name, child) in &self.children {
if name.starts_with('$') {
log::warn!(
"Keys starting with '$' are reserved by Rojo to ensure forward compatibility."
);
log::warn!(
"This project uses the key '{}', which should be renamed.",
name
);
}
child.validate_reserved_names();
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn path_node_required() {
let path_node: PathNode = serde_json::from_str(r#""src""#).unwrap();
assert_eq!(path_node, PathNode::Required(PathBuf::from("src")));
}
#[test]
fn path_node_optional() {
let path_node: PathNode = serde_json::from_str(r#"{ "optional": "src" }"#).unwrap();
assert_eq!(
path_node,
PathNode::Optional(OptionalPathNode::new(PathBuf::from("src")))
);
}
#[test]
fn project_node_required() {
let project_node: ProjectNode = serde_json::from_str(
r#"{
"$path": "src"
}"#,
)
.unwrap();
assert_eq!(
project_node.path,
Some(PathNode::Required(PathBuf::from("src")))
);
}
#[test]
fn project_node_optional() {
let project_node: ProjectNode = serde_json::from_str(
r#"{
"$path": { "optional": "src" }
}"#,
)
.unwrap();
assert_eq!(
project_node.path,
Some(PathNode::Optional(OptionalPathNode::new(PathBuf::from(
"src"
))))
);
}
#[test]
fn project_node_none() {
let project_node: ProjectNode = serde_json::from_str(
r#"{
"$className": "Folder"
}"#,
)
.unwrap();
assert_eq!(project_node.path, None);
}
#[test]
fn project_node_optional_serialize_absolute() {
let project_node: ProjectNode = serde_json::from_str(
r#"{
"$path": { "optional": "..\\src" }
}"#,
)
.unwrap();
let serialized = serde_json::to_string(&project_node).unwrap();
assert_eq!(serialized, r#"{"$path":{"optional":"../src"}}"#);
}
#[test]
fn project_node_optional_serialize_absolute_no_change() {
let project_node: ProjectNode = serde_json::from_str(
r#"{
"$path": { "optional": "../src" }
}"#,
)
.unwrap();
let serialized = serde_json::to_string(&project_node).unwrap();
assert_eq!(serialized, r#"{"$path":{"optional":"../src"}}"#);
}
#[test]
fn project_node_optional_serialize_optional() {
let project_node: ProjectNode = serde_json::from_str(
r#"{
"$path": "..\\src"
}"#,
)
.unwrap();
let serialized = serde_json::to_string(&project_node).unwrap();
assert_eq!(serialized, r#"{"$path":"../src"}"#);
}
}

View File

@@ -0,0 +1,294 @@
use std::borrow::Borrow;
use anyhow::format_err;
use rbx_dom_weak::types::{
CFrame, Color3, Content, Enum, Matrix3, Tags, Variant, VariantType, Vector2, Vector3,
};
use rbx_reflection::{DataType, PropertyDescriptor};
use serde::{Deserialize, Serialize};
/// A user-friendly version of `Variant` that supports specifying ambiguous
/// values. Ambiguous values need a reflection database to be resolved to a
/// usable value.
///
/// This type is used in Rojo projects and JSON models to make specifying the
/// most common types of properties, like strings or vectors, much easier.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum UnresolvedValue {
FullyQualified(Variant),
Ambiguous(AmbiguousValue),
}
impl UnresolvedValue {
pub fn resolve(self, class_name: &str, prop_name: &str) -> anyhow::Result<Variant> {
match self {
UnresolvedValue::FullyQualified(full) => Ok(full),
UnresolvedValue::Ambiguous(partial) => partial.resolve(class_name, prop_name),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AmbiguousValue {
Bool(bool),
String(String),
StringArray(Vec<String>),
Number(f64),
Array2([f64; 2]),
Array3([f64; 3]),
Array4([f64; 4]),
Array12([f64; 12]),
}
impl AmbiguousValue {
pub fn resolve(self, class_name: &str, prop_name: &str) -> anyhow::Result<Variant> {
let property = find_descriptor(class_name, prop_name)
.ok_or_else(|| format_err!("Unknown property {}.{}", class_name, prop_name))?;
match &property.data_type {
DataType::Enum(enum_name) => {
let database = rbx_reflection_database::get();
let enum_descriptor = database.enums.get(enum_name).ok_or_else(|| {
format_err!("Unknown enum {}. This is a Rojo bug!", enum_name)
})?;
let error = |what: &str| {
let mut all_values = enum_descriptor
.items
.keys()
.map(|value| value.borrow())
.collect::<Vec<_>>();
all_values.sort();
let examples = nonexhaustive_list(&all_values);
format_err!(
"Invalid value for property {}.{}. Got {} but \
expected a member of the {} enum such as {}",
class_name,
prop_name,
what,
enum_name,
examples,
)
};
let value = match self {
AmbiguousValue::String(value) => value,
unresolved => return Err(error(unresolved.describe())),
};
let resolved = enum_descriptor
.items
.get(value.as_str())
.ok_or_else(|| error(value.as_str()))?;
Ok(Enum::from_u32(*resolved).into())
}
DataType::Value(variant_ty) => match (variant_ty, self) {
(VariantType::Bool, AmbiguousValue::Bool(value)) => Ok(value.into()),
(VariantType::Float32, AmbiguousValue::Number(value)) => Ok((value as f32).into()),
(VariantType::Float64, AmbiguousValue::Number(value)) => Ok(value.into()),
(VariantType::Int32, AmbiguousValue::Number(value)) => Ok((value as i32).into()),
(VariantType::Int64, AmbiguousValue::Number(value)) => Ok((value as i64).into()),
(VariantType::String, AmbiguousValue::String(value)) => Ok(value.into()),
(VariantType::Tags, AmbiguousValue::StringArray(value)) => {
Ok(Tags::from(value).into())
}
(VariantType::Content, AmbiguousValue::String(value)) => {
Ok(Content::from(value).into())
}
(VariantType::Vector2, AmbiguousValue::Array2(value)) => {
Ok(Vector2::new(value[0] as f32, value[1] as f32).into())
}
(VariantType::Vector3, AmbiguousValue::Array3(value)) => {
Ok(Vector3::new(value[0] as f32, value[1] as f32, value[2] as f32).into())
}
(VariantType::Color3, AmbiguousValue::Array3(value)) => {
Ok(Color3::new(value[0] as f32, value[1] as f32, value[2] as f32).into())
}
(VariantType::CFrame, AmbiguousValue::Array12(value)) => {
let value = value.map(|v| v as f32);
let pos = Vector3::new(value[0], value[1], value[2]);
let orientation = Matrix3::new(
Vector3::new(value[3], value[4], value[5]),
Vector3::new(value[6], value[7], value[8]),
Vector3::new(value[9], value[10], value[11]),
);
Ok(CFrame::new(pos, orientation).into())
}
(_, unresolved) => Err(format_err!(
"Wrong type of value for property {}.{}. Expected {:?}, got {}",
class_name,
prop_name,
variant_ty,
unresolved.describe(),
)),
},
_ => Err(format_err!(
"Unknown data type for property {}.{}",
class_name,
prop_name
)),
}
}
fn describe(&self) -> &'static str {
match self {
AmbiguousValue::Bool(_) => "a bool",
AmbiguousValue::String(_) => "a string",
AmbiguousValue::StringArray(_) => "an array of strings",
AmbiguousValue::Number(_) => "a number",
AmbiguousValue::Array2(_) => "an array of two numbers",
AmbiguousValue::Array3(_) => "an array of three numbers",
AmbiguousValue::Array4(_) => "an array of four numbers",
AmbiguousValue::Array12(_) => "an array of twelve numbers",
}
}
}
fn find_descriptor(
class_name: &str,
prop_name: &str,
) -> Option<&'static PropertyDescriptor<'static>> {
let database = rbx_reflection_database::get();
let mut current_class_name = class_name;
loop {
let class = database.classes.get(current_class_name)?;
if let Some(descriptor) = class.properties.get(prop_name) {
return Some(descriptor);
}
current_class_name = class.superclass.as_deref()?;
}
}
/// Outputs a string containing up to MAX_ITEMS entries from the given list. If
/// there are more than MAX_ITEMS items, the number of remaining items will be
/// listed.
fn nonexhaustive_list(values: &[&str]) -> String {
use std::fmt::Write;
const MAX_ITEMS: usize = 8;
let mut output = String::new();
let last_index = values.len() - 1;
let main_length = last_index.min(9);
let main_list = &values[..main_length];
for value in main_list {
output.push_str(value);
output.push_str(", ");
}
if values.len() > MAX_ITEMS {
write!(output, "or {} more", values.len() - main_length).unwrap();
} else {
output.push_str("or ");
output.push_str(values[values.len() - 1]);
}
output
}
#[cfg(test)]
mod test {
use super::*;
fn resolve(class: &str, prop: &str, json_value: &str) -> Variant {
let unresolved: UnresolvedValue = serde_json::from_str(json_value).unwrap();
unresolved.resolve(class, prop).unwrap()
}
#[test]
fn bools() {
assert_eq!(resolve("BoolValue", "Value", "false"), Variant::Bool(false));
// Script.Disabled is inherited from BaseScript
assert_eq!(resolve("Script", "Disabled", "true"), Variant::Bool(true));
}
#[test]
fn strings() {
// String literals can stay as strings
assert_eq!(
resolve("StringValue", "Value", "\"Hello!\""),
Variant::String("Hello!".into()),
);
// String literals can also turn into Content
assert_eq!(
resolve("Sky", "MoonTextureId", "\"rbxassetid://12345\""),
Variant::Content("rbxassetid://12345".into()),
);
// What about BinaryString values? For forward-compatibility reasons, we
// don't support any shorthands for BinaryString.
//
// assert_eq!(
// resolve("Folder", "Tags", "\"a\\u0000b\\u0000c\""),
// Variant::BinaryString(b"a\0b\0c".to_vec().into()),
// );
}
#[test]
fn numbers() {
assert_eq!(
resolve("Part", "CollisionGroupId", "123"),
Variant::Int32(123),
);
assert_eq!(
resolve("Folder", "SourceAssetId", "532413"),
Variant::Int64(532413),
);
assert_eq!(resolve("Part", "Transparency", "1"), Variant::Float32(1.0));
assert_eq!(resolve("NumberValue", "Value", "1"), Variant::Float64(1.0));
}
#[test]
fn vectors() {
assert_eq!(
resolve("ParticleEmitter", "SpreadAngle", "[1, 2]"),
Variant::Vector2(Vector2::new(1.0, 2.0)),
);
assert_eq!(
resolve("Part", "Position", "[4, 5, 6]"),
Variant::Vector3(Vector3::new(4.0, 5.0, 6.0)),
);
}
#[test]
fn colors() {
assert_eq!(
resolve("Part", "Color", "[1, 1, 1]"),
Variant::Color3(Color3::new(1.0, 1.0, 1.0)),
);
// There aren't any user-facing Color3uint8 properties. If there are
// some, we should treat them the same in the future.
}
#[test]
fn enums() {
assert_eq!(
resolve("Lighting", "Technology", "\"Voxel\""),
Variant::Enum(Enum::from_u32(1)),
);
}
}

4
foreman.toml Normal file
View File

@@ -0,0 +1,4 @@
[tools]
rojo = { source = "rojo-rbx/rojo", version = "7.1.1" }
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
selene = { source = "Kampfkarren/selene", version = "0.17.0" }

View File

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

1
plugin/modules/roact Submodule

Submodule plugin/modules/roact added at f7d2f1ce1d

1
plugin/modules/t Submodule

Submodule plugin/modules/t added at f643b50682

1
plugin/modules/testez Submodule

Submodule plugin/modules/testez added at 25d957d4d5

View File

@@ -23,45 +23,8 @@ 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 = {}
@@ -470,6 +433,8 @@ types = {
}, },
} }
local EncodedValue = {}
function EncodedValue.decode(encodedValue) function EncodedValue.decode(encodedValue)
local ty, value = next(encodedValue) local ty, value = next(encodedValue)
@@ -494,19 +459,4 @@ function EncodedValue.encode(rbxValue, propertyType)
} }
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

@@ -1,73 +1,4 @@
{ {
"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": {
"Axes": [ "Axes": [

View File

@@ -5,26 +5,6 @@ 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 = {
Attributes = {
read = function(instance)
return true, instance:GetAttributes()
end,
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 = { Tags = {
read = function(instance) read = function(instance)
return true, CollectionService:GetTags(instance) return true, CollectionService:GetTags(instance)

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TestEZ = require(ReplicatedStorage.Packages.TestEZ) local TestEZ = require(ReplicatedStorage.TestEZ)
local Rojo = ReplicatedStorage.Rojo local Rojo = ReplicatedStorage.Rojo
@@ -16,4 +16,4 @@ require(Rojo.Plugin.runTests)(TestEZ)
if setDevSettings then if setDevSettings then
DevSettings:resetValues() DevSettings:resetValues()
end end

View File

@@ -1,7 +1,6 @@
local Packages = script.Parent.Parent.Packages local Http = require(script.Parent.Parent.Http)
local Http = require(Packages.Http) local Log = require(script.Parent.Parent.Log)
local Log = require(Packages.Log) local Promise = require(script.Parent.Parent.Promise)
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)
@@ -86,7 +85,7 @@ local ApiContext = {}
ApiContext.__index = ApiContext ApiContext.__index = ApiContext
function ApiContext.new(baseUrl) function ApiContext.new(baseUrl)
assert(type(baseUrl) == "string", "baseUrl must be a string") assert(type(baseUrl) == "string")
local self = { local self = {
__baseUrl = baseUrl, __baseUrl = baseUrl,
@@ -249,4 +248,4 @@ function ApiContext:open(id)
end) end)
end end
return ApiContext return ApiContext

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
@@ -39,4 +38,4 @@ local function BorderedContainer(props)
end) end)
end end
return BorderedContainer return BorderedContainer

View File

@@ -1,9 +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(Packages.Roact) local Roact = require(Rojo.Roact)
local Flipper = require(Packages.Flipper) local Flipper = require(Rojo.Flipper)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
@@ -94,4 +93,4 @@ function Checkbox:render()
end) end)
end end
return Checkbox return Checkbox

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
@@ -53,4 +52,4 @@ local function Header(props)
end) end)
end end
return Header return Header

View File

@@ -1,9 +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(Packages.Roact) local Roact = require(Rojo.Roact)
local Flipper = require(Packages.Flipper) local Flipper = require(Rojo.Flipper)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local bindingUtil = require(Plugin.App.bindingUtil) local bindingUtil = require(Plugin.App.bindingUtil)
@@ -77,4 +76,4 @@ function IconButton:render()
}) })
end end
return IconButton return IconButton

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
@@ -40,4 +39,4 @@ local function ScrollingFrame(props)
end) end)
end end
return ScrollingFrame return ScrollingFrame

View File

@@ -1,7 +1,6 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages
local Roact = require(Packages.Roact) local Roact = require(Rojo.Roact)
local e = Roact.createElement local e = Roact.createElement
@@ -27,4 +26,4 @@ local function SlicedImage(props)
}, props[Roact.Children]) }, props[Roact.Children])
end end
return SlicedImage return SlicedImage

View File

@@ -2,9 +2,8 @@ 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(Packages.Roact) local Roact = require(Rojo.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
@@ -64,4 +63,4 @@ function Spinner:willUnmount()
self.stepper:Disconnect() self.stepper:Disconnect()
end end
return Spinner return Spinner

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)

View File

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

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)
@@ -82,4 +81,4 @@ local function StudioPluginGuiWrapper(props)
}) })
end end
return StudioPluginGuiWrapper return StudioPluginGuiWrapper

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)
@@ -43,4 +42,4 @@ local function StudioToolbarWrapper(props)
}) })
end end
return StudioToolbarWrapper return StudioToolbarWrapper

View File

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

View File

@@ -2,10 +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(Packages.Roact) local Roact = require(Rojo.Roact)
local Flipper = require(Packages.Flipper) local Flipper = require(Rojo.Flipper)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
@@ -135,4 +134,4 @@ function TextButton:render()
end) end)
end end
return TextButton return TextButton

View File

@@ -1,9 +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(Packages.Roact) local Roact = require(Rojo.Roact)
local Flipper = require(Packages.Flipper) local Flipper = require(Rojo.Flipper)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local bindingUtil = require(Plugin.App.bindingUtil) local bindingUtil = require(Plugin.App.bindingUtil)
@@ -143,4 +142,4 @@ function TouchRipple:render()
}) })
end end
return TouchRipple return TouchRipple

View File

@@ -1,199 +0,0 @@
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,9 +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(Packages.Roact) local Roact = require(Rojo.Roact)
local Flipper = require(Packages.Flipper) local Flipper = require(Rojo.Flipper)
local Dictionary = require(Plugin.Dictionary) local Dictionary = require(Plugin.Dictionary)
@@ -68,4 +67,4 @@ function Page:didUpdate(lastProps)
end end
end end
return Page return Page

View File

@@ -0,0 +1,121 @@
--[[
Persistent plugin settings that can be accessed via Roact context.
]]
local Rojo = script:FindFirstAncestor("Rojo")
local Roact = require(Rojo.Roact)
local defaultSettings = {
openScriptsExternally = false,
twoWaySync = false,
}
local Settings = {}
Settings.__index = Settings
function Settings.fromPlugin(plugin)
local values = {}
for name, defaultValue in pairs(defaultSettings) do
local savedValue = plugin:GetSetting("Rojo_" .. name)
if savedValue == nil then
plugin:SetSetting("Rojo_" .. name, defaultValue)
values[name] = defaultValue
else
values[name] = savedValue
end
end
return setmetatable({
__values = values,
__plugin = plugin,
__updateListeners = {},
}, Settings)
end
function Settings:get(name)
if defaultSettings[name] == nil then
error("Invalid setings name " .. tostring(name), 2)
end
return self.__values[name]
end
function Settings:set(name, value)
self.__plugin:SetSetting("Rojo_" .. name, value)
self.__values[name] = value
for callback in pairs(self.__updateListeners) do
callback(name, value)
end
end
function Settings:onUpdate(newCallback)
local newListeners = {}
for callback in pairs(self.__updateListeners) do
newListeners[callback] = true
end
newListeners[newCallback] = true
self.__updateListeners = newListeners
return function()
local newListeners = {}
for callback in pairs(self.__updateListeners) do
if callback ~= newCallback then
newListeners[callback] = true
end
end
self.__updateListeners = newListeners
end
end
local Context = Roact.createContext(nil)
local StudioProvider = Roact.Component:extend("StudioProvider")
function StudioProvider:init()
self.settings = Settings.fromPlugin(self.props.plugin)
end
function StudioProvider:render()
return Roact.createElement(Context.Provider, {
value = self.settings,
}, self.props[Roact.Children])
end
local InternalConsumer = Roact.Component:extend("InternalConsumer")
function InternalConsumer:render()
return self.props.render(self.props.settings)
end
function InternalConsumer:didMount()
self.disconnect = self.props.settings:onUpdate(function()
-- Trigger a dummy state update to update the settings consumer.
self:setState({})
end)
end
function InternalConsumer:willUnmount()
self.disconnect()
end
local function with(callback)
return Roact.createElement(Context.Consumer, {
render = function(settings)
return Roact.createElement(InternalConsumer, {
settings = settings,
render = callback,
})
end,
})
end
return {
StudioProvider = StudioProvider,
with = with,
}

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
@@ -13,26 +12,6 @@ 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, {
@@ -103,59 +82,33 @@ end
local ConnectedPage = Roact.Component:extend("ConnectedPage") local ConnectedPage = Roact.Component:extend("ConnectedPage")
function ConnectedPage:render() function ConnectedPage:render()
return Theme.with(function(theme) return Roact.createFragment({
return Roact.createFragment({ Header = e(Header, {
Header = e(Header, { transparency = self.props.transparency,
transparency = self.props.transparency, layoutOrder = 1,
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,
}), }),
Info = e("TextLabel", { Layout = e("UIListLayout", {
Text = self.props.patchInfo:map(function(info) VerticalAlignment = Enum.VerticalAlignment.Center,
return string.format( FillDirection = Enum.FillDirection.Vertical,
"<i>Synced %d change%s %s</i>", SortOrder = Enum.SortOrder.LayoutOrder,
info.changes, Padding = UDim.new(0, 10),
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,
Size = UDim2.new(1, 0, 0, 28), Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, 20),
LayoutOrder = 3, PaddingRight = UDim.new(0, 20),
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)
@@ -169,4 +122,4 @@ function ConnectedPage.getDerivedStateFromProps(props)
} }
end end
return ConnectedPage return ConnectedPage

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Spinner = require(Plugin.App.Components.Spinner) local Spinner = require(Plugin.App.Components.Spinner)
@@ -18,4 +17,4 @@ function ConnectingPage:render()
}) })
end end
return ConnectingPage return ConnectingPage

View File

@@ -2,9 +2,8 @@ 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(Packages.Roact) local Roact = require(Rojo.Roact)
local Theme = require(Plugin.App.Theme) local Theme = require(Plugin.App.Theme)
@@ -57,16 +56,8 @@ function Error:render()
end, end,
}, { }, {
ErrorMessage = Theme.with(function(theme) ErrorMessage = Theme.with(function(theme)
return e("TextBox", { return e("TextLabel", {
[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,
@@ -74,9 +65,10 @@ 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,
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0), Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
}) })
end), end),
@@ -158,4 +150,4 @@ function ErrorPage.getDerivedStateFromProps(props)
} }
end end
return ErrorPage return ErrorPage

View File

@@ -1,8 +1,7 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Config = require(Plugin.Config) local Config = require(Plugin.Config)
@@ -149,4 +148,4 @@ function NotConnectedPage:render()
}) })
end end
return NotConnectedPage return NotConnectedPage

View File

@@ -0,0 +1,230 @@
local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Roact = require(Rojo.Roact)
local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme)
local PluginSettings = require(Plugin.App.PluginSettings)
local Checkbox = require(Plugin.App.Components.Checkbox)
local IconButton = require(Plugin.App.Components.IconButton)
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
local e = Roact.createElement
local DIVIDER_FADE_SIZE = 0.1
local function getTextBounds(text, textSize, font, lineHeight, bounds)
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
local lineCount = textBounds.Y / textSize
local lineHeightAbsolute = textSize * lineHeight
return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
end
local function Navbar(props)
return Theme.with(function(theme)
theme = theme.Settings.Navbar
return e("Frame", {
Size = UDim2.new(1, 0, 0, 46),
LayoutOrder = props.layoutOrder,
BackgroundTransparency = 1,
}, {
Back = e(IconButton, {
icon = Assets.Images.Icons.Back,
iconSize = 24,
color = theme.BackButtonColor,
transparency = props.transparency,
position = UDim2.new(0, 0, 0.5, 0),
anchorPoint = Vector2.new(0, 0.5),
onClick = props.onBack,
}),
Text = e("TextLabel", {
Text = "Settings",
Font = Enum.Font.Gotham,
TextSize = 18,
TextColor3 = theme.TextColor,
TextTransparency = props.transparency,
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
})
})
end)
end
local Setting = Roact.Component:extend("Setting")
function Setting:init()
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
end
function Setting:render()
return Theme.with(function(theme)
theme = theme.Settings
return PluginSettings.with(function(settings)
return e("Frame", {
Size = self.contentSize:map(function(value)
return UDim2.new(1, 0, 0, 20 + value.Y + 20)
end),
LayoutOrder = self.props.layoutOrder,
BackgroundTransparency = 1,
[Roact.Change.AbsoluteSize] = function(object)
self.setContainerSize(object.AbsoluteSize)
end,
}, {
Checkbox = e(Checkbox, {
active = settings:get(self.props.id),
transparency = self.props.transparency,
position = UDim2.new(1, 0, 0.5, 0),
anchorPoint = Vector2.new(1, 0.5),
onClick = function()
local currentValue = settings:get(self.props.id)
settings:set(self.props.id, not currentValue)
end,
}),
Text = e("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
}, {
Name = e("TextLabel", {
Text = self.props.name,
Font = Enum.Font.GothamBold,
TextSize = 17,
TextColor3 = theme.Setting.NameColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
Size = UDim2.new(1, 0, 0, 17),
LayoutOrder = 1,
BackgroundTransparency = 1,
}),
Description = e("TextLabel", {
Text = self.props.description,
Font = Enum.Font.Gotham,
LineHeight = 1.2,
TextSize = 14,
TextColor3 = theme.Setting.DescriptionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
TextWrapped = true,
Size = self.containerSize:map(function(value)
local textBounds = getTextBounds(
self.props.description, 14, Enum.Font.Gotham, 1.2,
Vector2.new(value.X - 50, math.huge)
)
return UDim2.new(1, -50, 0, textBounds.Y)
end),
LayoutOrder = 2,
BackgroundTransparency = 1,
}),
Layout = e("UIListLayout", {
VerticalAlignment = Enum.VerticalAlignment.Center,
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 6),
[Roact.Change.AbsoluteContentSize] = function(object)
self.setContentSize(object.AbsoluteContentSize)
end,
}),
Padding = e("UIPadding", {
PaddingTop = UDim.new(0, 20),
PaddingBottom = UDim.new(0, 20),
}),
}),
Divider = e("Frame", {
BackgroundColor3 = theme.DividerColor,
BackgroundTransparency = self.props.transparency,
Size = UDim2.new(1, 0, 0, 1),
BorderSizePixel = 0,
}, {
Gradient = e("UIGradient", {
Transparency = NumberSequence.new({
NumberSequenceKeypoint.new(0, 1),
NumberSequenceKeypoint.new(DIVIDER_FADE_SIZE, 0),
NumberSequenceKeypoint.new(1 - DIVIDER_FADE_SIZE, 0),
NumberSequenceKeypoint.new(1, 1),
}),
}),
}),
})
end)
end)
end
local SettingsPage = Roact.Component:extend("SettingsPage")
function SettingsPage:init()
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
end
function SettingsPage:render()
return Theme.with(function(theme)
theme = theme.Settings
return e(ScrollingFrame, {
size = UDim2.new(1, 0, 1, 0),
contentSize = self.contentSize,
transparency = self.props.transparency,
}, {
Navbar = e(Navbar, {
onBack = self.props.onBack,
transparency = self.props.transparency,
layoutOrder = 0,
}),
OpenScriptsExternally = e(Setting, {
id = "openScriptsExternally",
name = "Open Scripts Externally",
description = "Attempt to open scripts in an external editor",
transparency = self.props.transparency,
layoutOrder = 1,
}),
TwoWaySync = e(Setting, {
id = "twoWaySync",
name = "Two-Way Sync",
description = "EXPERIMENTAL! Editing files in Studio will sync them into the filesystem",
transparency = self.props.transparency,
layoutOrder = 2,
}),
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
[Roact.Change.AbsoluteContentSize] = function(object)
self.setContentSize(object.AbsoluteContentSize)
end,
}),
Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, 20),
PaddingRight = UDim.new(0, 20),
}),
})
end)
end
return SettingsPage

View File

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

@@ -1,122 +0,0 @@
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,10 +16,9 @@ local function getStudio()
end end
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages
local Roact = require(Packages.Roact) local Roact = require(Rojo.Roact)
local Log = require(Packages.Log) local Log = require(Rojo.Log)
local strict = require(script.Parent.Parent.strict) local strict = require(script.Parent.Parent.strict)
@@ -104,10 +103,6 @@ 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),
}) })
@@ -182,10 +177,6 @@ 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,8 +1,7 @@
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages
local Roact = require(Packages.Roact) local Roact = require(Rojo.Roact)
local Log = require(Packages.Log) local Log = require(Rojo.Log)
local LERP_DATA_TYPES = { local LERP_DATA_TYPES = {
Color3 = true, Color3 = true,
@@ -56,4 +55,4 @@ return {
mapLerp = mapLerp, mapLerp = mapLerp,
deriveProperty = deriveProperty, deriveProperty = deriveProperty,
blendAlpha = blendAlpha, blendAlpha = blendAlpha,
} }

View File

@@ -1,27 +1,21 @@
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(Packages.Roact) local Roact = require(Rojo.Roact)
local Log = require(Packages.Log) local Log = require(Rojo.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 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)
@@ -46,45 +40,14 @@ function App:init()
self.host, self.setHost = Roact.createBinding("") self.host, self.setHost = Roact.createBinding("")
self.port, self.setPort = 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, toolbarIcon = Assets.Images.PluginButton,
}) })
end end
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() function App:getHostAndPort()
local host = self.host:getValue() local host = self.host:getValue()
local port = self.port:getValue() local port = self.port:getValue()
@@ -95,70 +58,12 @@ function App:getHostAndPort()
return host, port return host, port
end 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() 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 host, port = self:getHostAndPort()
local sessionOptions = { local sessionOptions = {
openScriptsExternally = Settings:get("openScriptsExternally"), openScriptsExternally = self.props.settings:get("openScriptsExternally"),
twoWaySync = Settings:get("twoWaySync"), twoWaySync = self.props.settings:get("twoWaySync"),
} }
local baseUrl = ("http://%s:%s"):format(host, port) local baseUrl = ("http://%s:%s"):format(host, port)
@@ -170,41 +75,12 @@ function App:startSession()
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, 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({
@@ -213,10 +89,10 @@ function App:startSession()
address = address, address = address,
toolbarIcon = Assets.Images.PluginButtonConnected, toolbarIcon = Assets.Images.PluginButtonConnected,
}) })
self:addNotification(string.format("Connected to session '%s' at %s.", details, address), 5)
Log.info("Connected to session '{}' at {}", details, address)
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.
@@ -228,13 +104,13 @@ function App:startSession()
errorMessage = tostring(details), errorMessage = tostring(details),
toolbarIcon = Assets.Images.PluginButtonWarning, 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, toolbarIcon = Assets.Images.PluginButton,
}) })
self:addNotification("Disconnected from session.")
Log.info("Disconnected session")
end end
end end
end) end)
@@ -242,16 +118,6 @@ function App:startSession()
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 end
function App:endSession() function App:endSession()
@@ -335,7 +201,6 @@ function App:render()
Connected = createPageElement(AppStatus.Connected, { Connected = createPageElement(AppStatus.Connected, {
projectName = self.state.projectName, projectName = self.state.projectName,
address = self.state.address, address = self.state.address,
patchInfo = self.patchInfo,
onDisconnect = function() onDisconnect = function()
self:endSession() self:endSession()
@@ -371,28 +236,6 @@ function App:render()
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, { toggleAction = e(StudioPluginAction, {
name = "RojoConnection", name = "RojoConnection",
title = "Rojo: Connect/Disconnect", title = "Rojo: Connect/Disconnect",
@@ -457,9 +300,14 @@ function App:render()
end end
return function(props) return function(props)
local mergedProps = Dictionary.merge(props, { return e(PluginSettings.StudioProvider, {
soundPlayer = soundPlayer.new(Settings), plugin = props.plugin,
}, {
App = PluginSettings.with(function(settings)
local settingsProps = Dictionary.merge(props, {
settings = settings,
})
return e(App, settingsProps)
end),
}) })
return e(App, mergedProps)
end end

View File

@@ -45,9 +45,6 @@ local Assets = {
[500] = "rbxassetid://2609138523" [500] = "rbxassetid://2609138523"
}, },
}, },
Sounds = {
Notification = "rbxassetid://203785492",
},
StartSession = "", StartSession = "",
SessionActive = "", SessionActive = "",
Configure = "", Configure = "",
@@ -65,4 +62,4 @@ end
guardForTypos("Assets", Assets) guardForTypos("Assets", Assets)
return Assets return Assets

View File

@@ -5,8 +5,7 @@
of instances) and return the patch. of instances) and return the patch.
]] ]]
local Packages = script.Parent.Parent.Parent.Packages local Log = require(script.Parent.Parent.Parent.Log)
local Log = require(Packages.Log)
local PatchSet = require(script.Parent.Parent.PatchSet) local PatchSet = require(script.Parent.Parent.PatchSet)

View File

@@ -1,6 +1,5 @@
local Packages = script.Parent.Parent.Parent.Packages local Log = require(script.Parent.Parent.Parent.Log)
local Log = require(Packages.Log) local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
local RbxDom = require(Packages.RbxDom)
local encodeProperty = require(script.Parent.encodeProperty) local encodeProperty = require(script.Parent.encodeProperty)

View File

@@ -1,6 +1,5 @@
local Packages = script.Parent.Parent.Parent.Packages local Log = require(script.Parent.Parent.Parent.Log)
local Log = require(Packages.Log) local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
local RbxDom = require(Packages.RbxDom)
return function(instance, propertyName, propertyDescriptor) return function(instance, propertyName, propertyDescriptor)
local readSuccess, readResult = propertyDescriptor:read(instance) local readSuccess, readResult = propertyDescriptor:read(instance)

View File

@@ -5,8 +5,8 @@ local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
return strict("Config", { return strict("Config", {
isDevBuild = isDevBuild, isDevBuild = isDevBuild,
codename = "Epiphany", codename = "Epiphany",
version = {7, 2, 1}, version = {7, 1, 1},
expectedServerVersionString = "7.2 or newer", expectedServerVersionString = "7.0 or newer",
protocolVersion = 4, protocolVersion = 4,
defaultHost = "localhost", defaultHost = "localhost",
defaultPort = 34872, defaultPort = 34872,

View File

@@ -1,7 +1,6 @@
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local Packages = script.Parent.Parent.Packages local Log = require(script.Parent.Parent.Log)
local Log = require(Packages.Log)
--[[ --[[
A bidirectional map between instance IDs and Roblox instances. It lets us A bidirectional map between instance IDs and Roblox instances. It lets us

View File

@@ -3,8 +3,7 @@
patch returned from the API. patch returned from the API.
]] ]]
local Packages = script.Parent.Parent.Packages local t = require(script.Parent.Parent.t)
local t = require(Packages.t)
local Types = require(script.Parent.Types) local Types = require(script.Parent.Types)
@@ -182,4 +181,4 @@ function PatchSet.humanSummary(instanceMap, patchSet)
return table.concat(statements, "\n") return table.concat(statements, "\n")
end end
return PatchSet return PatchSet

View File

@@ -2,8 +2,7 @@
Defines the errors that can be returned by the reconciler. Defines the errors that can be returned by the reconciler.
]] ]]
local Packages = script.Parent.Parent.Parent.Packages local Fmt = require(script.Parent.Parent.Parent.Fmt)
local Fmt = require(Packages.Fmt)
local Error = {} local Error = {}
@@ -35,4 +34,4 @@ function Error:__tostring()
return Fmt.fmt("Error({}): {:#?}", self.kind, self.details) return Fmt.fmt("Error({}): {:#?}", self.kind, self.details)
end end
return Error return Error

View File

@@ -5,8 +5,7 @@
Patches can come from the server or be generated by the client. Patches can come from the server or be generated by the client.
]] ]]
local Packages = script.Parent.Parent.Parent.Packages local Log = require(script.Parent.Parent.Parent.Log)
local Log = require(Packages.Log)
local PatchSet = require(script.Parent.Parent.PatchSet) local PatchSet = require(script.Parent.Parent.PatchSet)
local Types = require(script.Parent.Parent.Types) local Types = require(script.Parent.Parent.Types)

View File

@@ -3,8 +3,7 @@
usable by Rojo's reconciler, potentially using RbxDom. usable by Rojo's reconciler, potentially using RbxDom.
]] ]]
local Packages = script.Parent.Parent.Parent.Packages local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
local RbxDom = require(Packages.RbxDom)
local Error = require(script.Parent.Error) local Error = require(script.Parent.Error)
local function decodeValue(encodedValue, instanceMap) local function decodeValue(encodedValue, instanceMap)
@@ -39,4 +38,4 @@ local function decodeValue(encodedValue, instanceMap)
return true, decodedValue return true, decodedValue
end end
return decodeValue return decodeValue

View File

@@ -3,9 +3,7 @@
patch that can be later applied. patch that can be later applied.
]] ]]
local Packages = script.Parent.Parent.Parent.Packages local Log = require(script.Parent.Parent.Parent.Log)
local Log = require(Packages.Log)
local invariant = require(script.Parent.Parent.invariant) local invariant = require(script.Parent.Parent.invariant)
local getProperty = require(script.Parent.getProperty) local getProperty = require(script.Parent.getProperty)
local Error = require(script.Parent.Error) local Error = require(script.Parent.Error)
@@ -115,16 +113,6 @@ local function diff(instanceMap, virtualInstances, rootId)
local childId = instanceMap.fromInstances[childInstance] local childId = instanceMap.fromInstances[childInstance]
if childId == nil then if childId == nil then
-- pcall to avoid security permission errors
local success, skip = pcall(function()
-- We don't remove instances that aren't going to be saved anyway,
-- such as the Rojo session lock value.
return childInstance.Archivable == false
end)
if success and skip then
continue
end
-- This is an existing instance not present in the virtual DOM. -- This is an existing instance not present in the virtual DOM.
-- We can mark it for deletion unless the user has asked us not -- We can mark it for deletion unless the user has asked us not
-- to delete unknown stuff. -- to delete unknown stuff.
@@ -164,4 +152,4 @@ local function diff(instanceMap, virtualInstances, rootId)
return true, patch return true, patch
end end
return diff return diff

View File

@@ -1,6 +1,5 @@
return function() return function()
local Packages = script.Parent.Parent.Parent.Packages local Log = require(script.Parent.Parent.Parent.Log)
local Log = require(Packages.Log)
local InstanceMap = require(script.Parent.Parent.InstanceMap) local InstanceMap = require(script.Parent.Parent.InstanceMap)
local PatchSet = require(script.Parent.Parent.PatchSet) local PatchSet = require(script.Parent.Parent.PatchSet)
@@ -287,4 +286,4 @@ return function()
expect(size(patch.added)).to.equal(1) expect(size(patch.added)).to.equal(1)
expect(patch.added["CHILD"]).to.equal(virtualInstances["CHILD"]) expect(patch.added["CHILD"]).to.equal(virtualInstances["CHILD"])
end) end)
end end

View File

@@ -2,8 +2,7 @@
Attempts to read a property from the given instance. Attempts to read a property from the given instance.
]] ]]
local Packages = script.Parent.Parent.Parent.Packages local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
local RbxDom = require(Packages.RbxDom)
local Error = require(script.Parent.Error) local Error = require(script.Parent.Error)
local function getProperty(instance, propertyName) local function getProperty(instance, propertyName)
@@ -57,4 +56,4 @@ local function getProperty(instance, propertyName)
return true, valueOrErr return true, valueOrErr
end end
return getProperty return getProperty

View File

@@ -2,9 +2,8 @@
Attempts to set a property on the given instance. Attempts to set a property on the given instance.
]] ]]
local Packages = script.Parent.Parent.Parent.Packages local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
local Log = require(Packages.Log) local Log = require(script.Parent.Parent.Parent.Log)
local RbxDom = require(Packages.RbxDom)
local Error = require(script.Parent.Error) local Error = require(script.Parent.Error)
local function setProperty(instance, propertyName, value) local function setProperty(instance, propertyName, value)
@@ -46,4 +45,4 @@ local function setProperty(instance, propertyName, value)
return true return true
end end
return setProperty return setProperty

View File

@@ -1,10 +1,9 @@
local StudioService = game:GetService("StudioService") local StudioService = game:GetService("StudioService")
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local Packages = script.Parent.Parent.Packages local Log = require(script.Parent.Parent.Log)
local Log = require(Packages.Log) local Fmt = require(script.Parent.Parent.Fmt)
local Fmt = require(Packages.Fmt) local t = require(script.Parent.Parent.t)
local t = require(Packages.t)
local ChangeBatcher = require(script.Parent.ChangeBatcher) local ChangeBatcher = require(script.Parent.ChangeBatcher)
local InstanceMap = require(script.Parent.InstanceMap) local InstanceMap = require(script.Parent.InstanceMap)
@@ -95,7 +94,6 @@ function ServeSession.new(options)
__instanceMap = instanceMap, __instanceMap = instanceMap,
__changeBatcher = changeBatcher, __changeBatcher = changeBatcher,
__statusChangedCallback = nil, __statusChangedCallback = nil,
__patchAppliedCallback = nil,
__connections = connections, __connections = connections,
} }
@@ -123,10 +121,6 @@ function ServeSession:onStatusChanged(callback)
self.__statusChangedCallback = callback self.__statusChangedCallback = callback
end end
function ServeSession:onPatchApplied(callback)
self.__patchAppliedCallback = callback
end
function ServeSession:start() function ServeSession:start()
self:__setStatus(Status.Connecting) self:__setStatus(Status.Connecting)
@@ -143,9 +137,7 @@ function ServeSession:start()
end) end)
end) end)
:catch(function(err) :catch(function(err)
if self.__status ~= Status.Disconnected then self:__stopInternal(err)
self:__stopInternal(err)
end
end) end)
end end
@@ -239,10 +231,6 @@ function ServeSession:__initialSync(rootInstanceId)
Log.warn("Could not apply all changes requested by the Rojo server:\n{}", Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
PatchSet.humanSummary(self.__instanceMap, unappliedPatch)) PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
end end
if self.__patchAppliedCallback then
pcall(self.__patchAppliedCallback, catchUpPatch, unappliedPatch)
end
end) end)
end end
@@ -256,10 +244,6 @@ function ServeSession:__mainSyncLoop()
Log.warn("Could not apply all changes requested by the Rojo server:\n{}", Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
PatchSet.humanSummary(self.__instanceMap, unappliedPatch)) PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
end end
if self.__patchAppliedCallback then
pcall(self.__patchAppliedCallback, message, unappliedPatch)
end
end end
if self.__status ~= Status.Disconnected then if self.__status ~= Status.Disconnected then

View File

@@ -1,79 +0,0 @@
--[[
Persistent plugin settings.
]]
local plugin = plugin or script:FindFirstAncestorWhichIsA("Plugin")
local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages
local Log = require(Packages.Log)
local defaultSettings = {
openScriptsExternally = false,
twoWaySync = false,
showNotifications = true,
playSounds = true,
}
local Settings = {}
Settings._values = table.clone(defaultSettings)
Settings._updateListeners = {}
if plugin then
for name, defaultValue in pairs(Settings._values) do
local savedValue = plugin:GetSetting("Rojo_" .. name)
if savedValue == nil then
-- plugin:SetSetting hits disc instead of memory, so it can be slow. Spawn so we don't hang.
task.spawn(plugin.SetSetting, plugin, "Rojo_" .. name, defaultValue)
Settings._values[name] = defaultValue
else
Settings._values[name] = savedValue
end
end
Log.trace("Loaded settings from plugin store")
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._values[name] = value
if plugin then
-- plugin:SetSetting hits disc instead of memory, so it can be slow. Spawn so we don't hang.
task.spawn(plugin.SetSetting, plugin, "Rojo_" .. name, value)
end
if self._updateListeners[name] then
for callback in pairs(self._updateListeners[name]) do
task.spawn(callback, value)
end
end
Log.trace(string.format("Set setting '%s' to '%s'", name, tostring(value)))
end
function Settings:onChanged(name, callback)
local listeners = self._updateListeners[name]
if listeners == nil then
listeners = {}
self._updateListeners[name] = listeners
end
listeners[callback] = true
Log.trace(string.format("Added listener for setting '%s' changes", name))
return function()
listeners[callback] = nil
Log.trace(string.format("Removed listener for setting '%s' changes", name))
end
end
return Settings

View File

@@ -1,5 +1,5 @@
local Packages = script.Parent.Parent.Packages local t = require(script.Parent.Parent.t)
local t = require(Packages.t)
local DevSettings = require(script.Parent.DevSettings) local DevSettings = require(script.Parent.DevSettings)
local strict = require(script.Parent.strict) local strict = require(script.Parent.strict)

View File

@@ -2,24 +2,23 @@ if not plugin then
return return
end end
local Rojo = script:FindFirstAncestor("Rojo") local Log = require(script.Parent.Log)
local Packages = Rojo.Packages
local Log = require(Packages.Log)
local Roact = require(Packages.Roact)
local DevSettings = require(script.DevSettings) local DevSettings = require(script.DevSettings)
local Config = require(script.Config)
local App = require(script.App)
Log.setLogLevelThunk(function() Log.setLogLevelThunk(function()
return DevSettings:getLogLevel() return DevSettings:getLogLevel()
end) end)
local Roact = require(script.Parent.Roact)
local Config = require(script.Config)
local App = require(script.App)
local app = Roact.createElement(App, { local app = Roact.createElement(App, {
plugin = plugin plugin = plugin
}) })
local tree = Roact.mount(app, game:GetService("CoreGui"), "Rojo UI") local tree = Roact.mount(app, nil, "Rojo UI")
plugin.Unloading:Connect(function() plugin.Unloading:Connect(function()
Roact.unmount(tree) Roact.unmount(tree)
@@ -29,4 +28,4 @@ if Config.isDevBuild then
local TestEZ = require(script.Parent.TestEZ) local TestEZ = require(script.Parent.TestEZ)
require(script.runTests)(TestEZ) require(script.runTests)(TestEZ)
end end

View File

@@ -1,6 +1,4 @@
local Fmt = require(script.Parent.Parent.Fmt)
local Packages = script.Parent.Parent.Packages
local Fmt = require(Packages.Fmt)
local Config = require(script.Parent.Config) local Config = require(script.Parent.Config)
@@ -28,4 +26,4 @@ else
end end
end end
return invariant return invariant

View File

@@ -1,7 +1,6 @@
local ContentProvider = game:GetService("ContentProvider") local ContentProvider = game:GetService("ContentProvider")
local Packages = script.Parent.Parent.Packages local Log = require(script.Parent.Parent.Log)
local Log = require(Packages.Log)
local Assets = require(script.Parent.Assets) local Assets = require(script.Parent.Assets)
@@ -30,4 +29,4 @@ local function preloadAssets()
end)() end)()
end end
return preloadAssets return preloadAssets

View File

@@ -1,6 +1,5 @@
return function(TestEZ) return function(TestEZ)
local Rojo = script.Parent.Parent local Rojo = script.Parent.Parent
local Packages = Rojo.Packages
TestEZ.TestBootstrap:run({ Rojo.Plugin, Packages.Http, Packages.Log, Packages.RbxDom }) TestEZ.TestBootstrap:run({ Rojo.Plugin, Rojo.Http, Rojo.Log })
end end

View File

@@ -1,38 +0,0 @@
-- Sounds only play in Edit mode when parented to a plugin widget, for some reason
local plugin = plugin or script:FindFirstAncestorWhichIsA("Plugin")
local widget = nil
if plugin then
widget = plugin:CreateDockWidgetPluginGui("Rojo_soundPlayer", DockWidgetPluginGuiInfo.new(
Enum.InitialDockState.Float,
false, true,
10, 10,
10, 10
))
widget.Name = "Rojo_soundPlayer"
widget.Title = "Rojo Sound Player"
end
local SoundPlayer = {}
SoundPlayer.__index = SoundPlayer
function SoundPlayer.new(settings)
return setmetatable({
settings = settings,
}, SoundPlayer)
end
function SoundPlayer:play(soundId)
if self.settings and self.settings:get("playSounds") == false then return end
local sound = Instance.new("Sound")
sound.SoundId = soundId
sound.Parent = widget
sound.Ended:Connect(function()
sound:Destroy()
end)
sound:Play()
end
return SoundPlayer

View File

@@ -8,8 +8,8 @@
"$path": "default.project.json" "$path": "default.project.json"
}, },
"Packages": { "TestEZ": {
"$path": "DevPackages" "$path": "modules/testez"
} }
}, },

View File

@@ -1,33 +0,0 @@
# This file is automatically @generated by Wally.
# It is not intended for manual editing.
registry = "test"
[[package]]
name = "evaera/promise"
version = "4.0.0"
dependencies = []
[[package]]
name = "osyrisrblx/t"
version = "3.0.0"
dependencies = []
[[package]]
name = "reselim/flipper"
version = "2.0.0"
dependencies = []
[[package]]
name = "roblox/roact"
version = "1.4.4"
dependencies = []
[[package]]
name = "roblox/testez"
version = "0.4.1"
dependencies = []
[[package]]
name = "rojo-rbx/rojo"
version = "7.2.1"
dependencies = [["Flipper", "reselim/flipper@2.0.0"], ["Promise", "evaera/promise@4.0.0"], ["Roact", "roblox/roact@1.4.4"], ["t", "osyrisrblx/t@3.0.0"]]

View File

@@ -1,17 +0,0 @@
[package]
name = "rojo-rbx/rojo"
description = "Rojo enables Roblox developers to use professional-grade software engineering tools"
version = "7.2.1"
license = "MPL-2.0"
authors = ["LPGhatguy (https://lpg.space/)"]
registry = "https://github.com/upliftgames/wally-index"
realm = "shared"
[dependencies]
Flipper = "reselim/flipper@2.0.0"
Promise = "evaera/promise@4.0.0"
Roact = "roblox/roact@1.4.4"
t = "osyrisrblx/t@3.0.0"
[dev-dependencies]
TestEZ = "roblox/testez@0.4.1"

View File

@@ -1,35 +0,0 @@
---
source: tests/tests/build.rs
expression: contents
---
<roblox version="4">
<Item class="Folder" referent="0">
<Properties>
<string name="Name">attributes</string>
</Properties>
<Item class="Folder" referent="1">
<Properties>
<string name="Name">Explicit</string>
<BinaryString name="AttributesSerialize">DQAAAAQAAABCb29sAwEKAAAAQnJpY2tDb2xvcg4BAAAABgAAAENvbG9yMw8AAAAAAAAAAAAAAAANAAAAQ29sb3JTZXF1ZW5jZRkCAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAACAPwAAgD8AAIA/AACAPwcAAABGbG9hdDMyBQAAAAAHAAAARmxvYXQ2NAYAAAAAAAAAAAsAAABOdW1iZXJSYW5nZRsAAAAAAAAAAA4AAABOdW1iZXJTZXF1ZW5jZRcCAAAAAAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAABAAAAFJlY3QcAAAAAAAAAAAAAAAAAAAAAAQAAABVRGltCQAAAAAAAAAABQAAAFVEaW0yCgAAAAAAAAAAAAAAAAAAAAAHAAAAVmVjdG9yMhAAAAAAAAAAAAcAAABWZWN0b3IzEQAAAAAAAAAAAAAAAA==</BinaryString>
</Properties>
</Item>
<Item class="Folder" referent="2">
<Properties>
<string name="Name">Implicit</string>
<BinaryString name="AttributesSerialize">AwAAAAQAAABCb29sAwEGAAAATnVtYmVyBgAAAAAAAOA/BgAAAFN0cmluZwIEAAAAVGVzdA==</BinaryString>
</Properties>
</Item>
<Item class="Folder" referent="3">
<Properties>
<string name="Name">LegacyExplicit</string>
<BinaryString name="AttributesSerialize">AgAAAAUAAABIZWxsbwIFAAAAV29ybGQGAAAAVmVjdG9yEQAAgD8AAABAAABAQA==</BinaryString>
</Properties>
</Item>
<Item class="Folder" referent="4">
<Properties>
<string name="Name">LegacyImplicit</string>
<BinaryString name="AttributesSerialize">AgAAAAMAAABIZXkCBwAAAEdyYW5kbWEGAAAAVmVjdG9yEQAAgEAAAKBAAADAQA==</BinaryString>
</Properties>
</Item>
</Item>
</roblox>

View File

@@ -1,19 +0,0 @@
---
source: tests/tests/build.rs
assertion_line: 100
expression: contents
---
<roblox version="4">
<Item class="LocalizationTable" referent="0">
<Properties>
<string name="Name">init_csv_with_children</string>
<string name="Contents">[{"key":"init.csv","values":{}}]</string>
</Properties>
<Item class="LocalizationTable" referent="1">
<Properties>
<string name="Name">other</string>
<string name="Contents">[{"key":"other.csv","values":{}}]</string>
</Properties>
</Item>
</Item>
</roblox>

View File

@@ -1,14 +0,0 @@
---
source: tests/tests/build.rs
assertion_line: 98
expression: contents
---
<roblox version="4">
<Item class="LocalScript" referent="0">
<Properties>
<string name="Name">issue_546</string>
<bool name="Disabled">true</bool>
<string name="Source">print("Hello, world!")</string>
</Properties>
</Item>
</roblox>

View File

@@ -1,13 +1,14 @@
--- ---
source: tests/tests/build.rs source: tests/tests/build.rs
assertion_line: 99
expression: contents expression: contents
--- ---
<roblox version="4"> <roblox version="4">
<Item class="Folder" referent="0"> <Item class="Folder" referent="0">
<Properties> <Properties>
<string name="Name">weldconstraint</string> <string name="Name">weldconstraint</string>
<BinaryString name="AttributesSerialize"></BinaryString> <BinaryString name="AttributesSerialize">
</BinaryString>
<int64 name="SourceAssetId">-1</int64> <int64 name="SourceAssetId">-1</int64>
<BinaryString name="Tags"></BinaryString> <BinaryString name="Tags"></BinaryString>
</Properties> </Properties>
@@ -15,7 +16,8 @@ expression: contents
<Properties> <Properties>
<string name="Name">A</string> <string name="Name">A</string>
<bool name="Anchored">false</bool> <bool name="Anchored">false</bool>
<BinaryString name="AttributesSerialize"></BinaryString> <BinaryString name="AttributesSerialize">
</BinaryString>
<float name="BackParamA">-0.5</float> <float name="BackParamA">-0.5</float>
<float name="BackParamB">0.5</float> <float name="BackParamB">0.5</float>
<token name="BackSurface">0</token> <token name="BackSurface">0</token>
@@ -106,7 +108,8 @@ expression: contents
<Item class="WeldConstraint" referent="2"> <Item class="WeldConstraint" referent="2">
<Properties> <Properties>
<string name="Name">WeldConstraint</string> <string name="Name">WeldConstraint</string>
<BinaryString name="AttributesSerialize"></BinaryString> <BinaryString name="AttributesSerialize">
</BinaryString>
<CoordinateFrame name="CFrame0"> <CoordinateFrame name="CFrame0">
<X>7</X> <X>7</X>
<Y>0.000001013279</Y> <Y>0.000001013279</Y>
@@ -133,7 +136,8 @@ expression: contents
<Properties> <Properties>
<string name="Name">B</string> <string name="Name">B</string>
<bool name="Anchored">false</bool> <bool name="Anchored">false</bool>
<BinaryString name="AttributesSerialize"></BinaryString> <BinaryString name="AttributesSerialize">
</BinaryString>
<float name="BackParamA">-0.5</float> <float name="BackParamA">-0.5</float>
<float name="BackParamB">0.5</float> <float name="BackParamB">0.5</float>
<token name="BackSurface">0</token> <token name="BackSurface">0</token>

View File

@@ -1,114 +0,0 @@
{
"name": "attributes",
"tree": {
"$className": "Folder",
"Implicit": {
"$className": "Folder",
"$attributes": {
"Bool": true,
"Number": 0.5,
"String": "Test"
}
},
"Explicit": {
"$className": "Folder",
"$attributes": {
"Bool": {
"Bool": true
},
"Float32": {
"Float32": 0
},
"Float64": {
"Float64": 0
},
"UDim": {
"UDim": [0, 0]
},
"UDim2": {
"UDim2": [[0, 0], [0, 0]]
},
"BrickColor": {
"BrickColor": 1
},
"Color3": {
"Color3": [0, 0, 0]
},
"Vector2": {
"Vector2": [0, 0]
},
"Vector3": {
"Vector3": [0, 0, 0]
},
"NumberSequence": {
"NumberSequence": {
"keypoints": [
{
"time": 0,
"value": 0,
"envelope": 0
},
{
"time": 1,
"value": 0,
"envelope": 0
}
]
}
},
"ColorSequence": {
"ColorSequence": {
"keypoints": [
{
"time": 0,
"color": [1, 1, 1]
},
{
"time": 1,
"color": [1, 1, 1]
}
]
}
},
"NumberRange": {
"NumberRange": [0, 0]
},
"Rect": {
"Rect": [[0, 0], [0, 0]]
}
}
},
"LegacyExplicit": {
"$className": "Folder",
"$properties": {
"Attributes": {
"Attributes": {
"Hello": {
"String": "World"
},
"Vector": {
"Vector3": [1, 2, 3]
}
}
}
}
},
"LegacyImplicit": {
"$className": "Folder",
"$properties": {
"Attributes": {
"Hey": {
"String": "Grandma"
},
"Vector": {
"Vector3": [4, 5, 6]
}
}
}
}
}
}

View File

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

View File

@@ -1,2 +0,0 @@
Key
init.csv
1 Key
2 init.csv

View File

@@ -1,2 +0,0 @@
Key
other.csv
1 Key
2 other.csv

View File

@@ -1,2 +0,0 @@
# Issue #546 (https://github.com/rojo-rbx/rojo/issues/546)
Regression from Rojo 6.2.0 to Rojo 7.0.0. Meta files named as init.meta.json should apply after init.client.lua and other init files.

View File

@@ -1,6 +0,0 @@
{
"name": "issue_546",
"tree": {
"$path": "hello"
}
}

View File

@@ -1 +0,0 @@
print("Hello, world!")

View File

@@ -1,5 +0,0 @@
{
"properties": {
"Disabled": true
}
}

View File

@@ -154,9 +154,7 @@ impl JobThreadContext {
for id in affected_ids { for id in affected_ids {
if let Some(patch) = compute_and_apply_changes(&mut tree, &self.vfs, id) { if let Some(patch) = compute_and_apply_changes(&mut tree, &self.vfs, id) {
if !patch.is_empty() { applied_patches.push(patch);
applied_patches.push(patch);
}
} }
} }
} }
@@ -255,9 +253,7 @@ impl JobThreadContext {
apply_patch_set(&mut tree, patch_set) apply_patch_set(&mut tree, patch_set)
}; };
if !applied_patch.is_empty() { self.message_queue.push_messages(&[applied_patch]);
self.message_queue.push_messages(&[applied_patch]);
}
} }
} }
@@ -295,7 +291,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
} }
}; };
let patch_set = compute_patch_set(snapshot, &tree, id); let patch_set = compute_patch_set(snapshot.as_ref(), &tree, id);
apply_patch_set(tree, patch_set) apply_patch_set(tree, patch_set)
} }
Ok(None) => { Ok(None) => {
@@ -338,7 +334,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
} }
}; };
let patch_set = compute_patch_set(snapshot, &tree, id); let patch_set = compute_patch_set(snapshot.as_ref(), &tree, id);
apply_patch_set(tree, patch_set) apply_patch_set(tree, patch_set)
} }
}; };

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