Merge branch 'master' into cli-serde-default
This commit is contained in:
159 files changed
+2545
-6250
No files matched your search
@@ -1,3 +1,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
use crate::writer::*;
|
||||
|
||||
@@ -11,13 +11,10 @@ keywords = ["dom", "ui", "gui", "react"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-rsx = { workspace = true }
|
||||
proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits", "visit"] }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
|
||||
prettyplease = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "2.0.3"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/dioxus-autofmt.svg
|
||||
[crates-url]: https://crates.io/crates/dioxus-autofmt
|
||||
[crates-url]: https://crates.io/crates/dioxus-check
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
|
||||
[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/learn/0.4/) |
|
||||
[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus_autofmt) |
|
||||
[API Docs](https://docs.rs/dioxus-check) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
mod check;
|
||||
mod issues;
|
||||
mod metadata;
|
||||
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
# .github/workflows/build.yml
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: release ${{ matrix.target }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
archive: tar.gz tar.xz
|
||||
- target: x86_64-unknown-linux-musl
|
||||
archive: tar.gz tar.xz
|
||||
- target: x86_64-apple-darwin
|
||||
archive: tar.gz tar.xz
|
||||
- target: x86_64-pc-windows-gnu
|
||||
archive: zip
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Compile and release
|
||||
uses: rust-build/rust-build.action@latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RUSTTARGET: ${{ matrix.target }}
|
||||
ARCHIVE_TYPES: ${{ matrix.archive }}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
name: github pages
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- docs/**
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-20.04
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
with:
|
||||
mdbook-version: '0.4.10'
|
||||
# mdbook-version: 'latest'
|
||||
|
||||
- run: cd docs && mdbook build
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@v4.2.3
|
||||
with:
|
||||
branch: gh-pages # The branch the action should deploy to.
|
||||
folder: docs/book # The folder the action should deploy.
|
||||
target-folder: docs/nightly/cli
|
||||
repository-name: dioxuslabs/docsite
|
||||
clean: false
|
||||
token: ${{ secrets.DEPLOY_KEY }} # let's pretend I don't need it for now
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
on: [push, pull_request]
|
||||
|
||||
name: Rust CI
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: cargo check
|
||||
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: cargo test
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: rustup component add rustfmt
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
# clippy:
|
||||
# name: Clippy
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: actions-rs/toolchain@v1
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: stable
|
||||
# override: true
|
||||
# - uses: Swatinem/rust-cache@v1
|
||||
# - run: rustup component add clippy
|
||||
# - uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: clippy
|
||||
# args: -- -D warnings
|
||||
Generated
-4778
File diff suppressed because it is too large.
Load diff
@@ -22,16 +22,13 @@ serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = "1.0.79"
|
||||
toml = "0.5.8"
|
||||
fs_extra = "1.2.0"
|
||||
cargo_toml = "0.11.4"
|
||||
cargo_toml = "0.16.0"
|
||||
futures = "0.3.21"
|
||||
notify = { version = "5.0.0-pre.16", features = ["serde"] }
|
||||
html_parser = { workspace = true }
|
||||
binary-install = "0.0.2"
|
||||
convert_case = "0.5.0"
|
||||
cargo_metadata = "0.15.0"
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
tokio = { version = "1.16.1", features = ["fs", "sync", "rt", "macros"] }
|
||||
atty = "0.2.14"
|
||||
regex = "1.5.4"
|
||||
chrono = "0.4.19"
|
||||
anyhow = "1.0.53"
|
||||
hyper = "0.14.17"
|
||||
@@ -59,7 +56,6 @@ tar = "0.4.38"
|
||||
zip = "0.6.2"
|
||||
tower = "0.4.12"
|
||||
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
proc-macro2 = { version = "1.0", features = ["span-locations"] }
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
# plugin packages
|
||||
@@ -71,7 +67,6 @@ mlua = { version = "0.8.1", features = [
|
||||
"macros",
|
||||
], optional = true }
|
||||
ctrlc = "3.2.3"
|
||||
gitignore = "1.0.7"
|
||||
open = "4.1.0"
|
||||
cargo-generate = "0.18"
|
||||
toml_edit = "0.19.11"
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
</div>
|
||||
|
||||
The **dioxus-cli** (inspired by wasm-pack and webpack) is a tool for getting Dioxus projects up and running.
|
||||
It handles all building, bundling, development and publishing to simplify development.
|
||||
It handles building, bundling, development and publishing to simplify development.
|
||||
|
||||
## Installation
|
||||
|
||||
### Install the stable version (recommended)
|
||||
|
||||
```
|
||||
cargo install dioxus-cli --locked
|
||||
cargo install dioxus-cli
|
||||
```
|
||||
|
||||
### Install the latest development build through git
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
book
|
||||
@@ -1,6 +0,0 @@
|
||||
[book]
|
||||
authors = ["YuKun Liu"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Dioxus CLI"
|
||||
@@ -1,13 +0,0 @@
|
||||
# Summary
|
||||
|
||||
- [Introduction](./introduction.md)
|
||||
- [Installation](./installation.md)
|
||||
- [Create a project](./creating.md)
|
||||
- [Configure a project](./configure.md)
|
||||
- [Plugin development](./plugin/README.md)
|
||||
- [API.Log](plugin/interface/log.md)
|
||||
- [API.Command](plugin/interface/command.md)
|
||||
- [API.OS](plugin/interface/os.md)
|
||||
- [API.Directories](plugin/interface/dirs.md)
|
||||
- [API.Network](plugin/interface/network.md)
|
||||
- [API.Path](plugin/interface/path.md)
|
||||
@@ -1,197 +0,0 @@
|
||||
# Configure Project
|
||||
|
||||
This chapter will teach you how to configure the CLI with the `Dioxus.toml` file.
|
||||
There's an [example](#config-example) which has comments to describe individual keys.
|
||||
You can copy that or view this documentation for a more complete learning experience.
|
||||
|
||||
"🔒" indicates a mandatory item. Some headers are mandatory, but none of the keys inside them are. It might look weird, but it's normal. Simply don't include any keys.
|
||||
|
||||
## Structure
|
||||
|
||||
Each header has it's TOML form directly under it.
|
||||
|
||||
### Application 🔒
|
||||
|
||||
```toml
|
||||
[application]
|
||||
```
|
||||
|
||||
Application-wide configuration. Applies to both web and desktop.
|
||||
|
||||
1. **name** 🔒 - Project name & title.
|
||||
```toml
|
||||
name = "my_project"
|
||||
```
|
||||
|
||||
2. **default_platform** 🔒 - The platform this project targets
|
||||
```toml
|
||||
# Currently supported platforms: web, desktop
|
||||
default_platform = "web"
|
||||
```
|
||||
|
||||
3. **out_dir** - The directory to place the build artifacts from `dx build` or `dx serve` into. This is also where the `assets` directory will be copied into.
|
||||
```toml
|
||||
out_dir = "dist"
|
||||
```
|
||||
|
||||
4. **asset_dir** - The directory with your static assets. The CLI will automatically copy these assets into the **out_dir** after a build/serve.
|
||||
```toml
|
||||
asset_dir = "public"
|
||||
```
|
||||
|
||||
5. **sub_package** - The sub package in the workspace to build by default.
|
||||
```toml
|
||||
sub_package = "my-crate"
|
||||
```
|
||||
|
||||
### Web.App 🔒
|
||||
|
||||
```toml
|
||||
[web.app]
|
||||
```
|
||||
|
||||
Web-specific configuration.
|
||||
|
||||
1. **title** - The title of the web page.
|
||||
```toml
|
||||
# HTML title tag content
|
||||
title = "project_name"
|
||||
```
|
||||
|
||||
2. **base_path** - The base path to build the application for serving at. This can be useful when serving your application in a subdirectory under a domain. For example when building a site to be served on GitHub Pages.
|
||||
```toml
|
||||
# The application will be served at domain.com/my_application/, so we need to modify the base_path to the path where the application will be served
|
||||
base_path = "my_application"
|
||||
```
|
||||
|
||||
### Web.Watcher ✍
|
||||
|
||||
```toml
|
||||
[web.watcher]
|
||||
```
|
||||
|
||||
Development server configuration.
|
||||
|
||||
1. **reload_html** - If this is true, the cli will rebuild the index.html file every time the application is rebuilt
|
||||
```toml
|
||||
reload_html = true
|
||||
```
|
||||
|
||||
2. **watch_path** - The files & directories to monitor for changes
|
||||
```toml
|
||||
watch_path = ["src", "public"]
|
||||
```
|
||||
|
||||
3. **index_on_404** - If enabled, Dioxus will serve the root page when a route is not found.
|
||||
*This is needed when serving an application that uses the router*.
|
||||
However, when serving your app using something else than Dioxus (e.g. GitHub Pages), you will have to check how to configure it on that platform.
|
||||
In GitHub Pages, you can make a copy of `index.html` named `404.html` in the same directory.
|
||||
```toml
|
||||
index_on_404 = true
|
||||
```
|
||||
|
||||
### Web.Resource 🔒
|
||||
|
||||
```toml
|
||||
[web.resource]
|
||||
```
|
||||
|
||||
Static resource configuration.
|
||||
|
||||
1. **style** - CSS files to include in your application.
|
||||
```toml
|
||||
style = [
|
||||
# Include from public_dir.
|
||||
"./assets/style.css",
|
||||
# Or some asset from online cdn.
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
|
||||
]
|
||||
```
|
||||
|
||||
2. **script** - JavaScript files to include in your application.
|
||||
```toml
|
||||
script = [
|
||||
# Include from asset_dir.
|
||||
"./public/index.js",
|
||||
# Or from an online CDN.
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
|
||||
]
|
||||
```
|
||||
|
||||
### Web.Resource.Dev 🔒
|
||||
|
||||
```toml
|
||||
[web.resource.dev]
|
||||
```
|
||||
|
||||
This is the same as [`[web.resource]`](#webresource-), but it only works in development servers.
|
||||
For example, if you want to include a file in a `dx serve` server, but not a `dx serve --release` server, put it here.
|
||||
|
||||
### Web.Proxy
|
||||
|
||||
```toml
|
||||
[[web.proxy]]
|
||||
```
|
||||
|
||||
Configuration related to any proxies your application requires during development. Proxies will forward requests to a new service.
|
||||
|
||||
1. **backend** - The URL to the server to proxy. The CLI will forward any requests under the backend relative route to the backend instead of returning 404
|
||||
```toml
|
||||
backend = "http://localhost:8000/api/"
|
||||
```
|
||||
This will cause any requests made to the dev server with prefix /api/ to be redirected to the backend server at http://localhost:8000. The path and query parameters will be passed on as-is (path rewriting is currently not supported).
|
||||
|
||||
## Config example
|
||||
|
||||
This includes all fields, mandatory or not.
|
||||
|
||||
```toml
|
||||
[application]
|
||||
|
||||
# App name
|
||||
name = "project_name"
|
||||
|
||||
# The Dioxus platform to default to
|
||||
default_platform = "web"
|
||||
|
||||
# `build` & `serve` output path
|
||||
out_dir = "dist"
|
||||
|
||||
# The static resource path
|
||||
asset_dir = "public"
|
||||
|
||||
[web.app]
|
||||
|
||||
# HTML title tag content
|
||||
title = "project_name"
|
||||
|
||||
[web.watcher]
|
||||
|
||||
# When watcher is triggered, regenerate the `index.html`
|
||||
reload_html = true
|
||||
|
||||
# Which files or dirs will be monitored
|
||||
watch_path = ["src", "public"]
|
||||
|
||||
# Include style or script assets
|
||||
[web.resource]
|
||||
|
||||
# CSS style file
|
||||
style = []
|
||||
|
||||
# Javascript code file
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# Same as [web.resource], but for development servers
|
||||
|
||||
# CSS style file
|
||||
style = []
|
||||
|
||||
# JavaScript files
|
||||
script = []
|
||||
|
||||
[[web.proxy]]
|
||||
backend = "http://localhost:8000/api/"
|
||||
```
|
||||
@@ -1,37 +0,0 @@
|
||||
# Create a Project
|
||||
|
||||
Once you have the Dioxus CLI installed, you can use it to create your own project!
|
||||
|
||||
## Initializing a default project
|
||||
|
||||
First, run the `dx create` command to create a new project:
|
||||
```
|
||||
dx create hello-dioxus
|
||||
```
|
||||
|
||||
> It will clone this [template](https://github.com/DioxusLabs/dioxus-template).
|
||||
> This default template is used for `web` platform application.
|
||||
>
|
||||
> You can choose to create your project from a different template by passing the `template` argument:
|
||||
> ```
|
||||
> dx init hello-dioxus --template=gh:dioxuslabs/dioxus-template
|
||||
> ```
|
||||
|
||||
Next, navigate into your new project:
|
||||
|
||||
```
|
||||
cd hello-dioxus
|
||||
```
|
||||
|
||||
> Make sure the WASM target is installed before running the projects.
|
||||
> You can install the WASM target for rust using rustup:
|
||||
> ```
|
||||
> rustup target add wasm32-unknown-unknown
|
||||
> ```
|
||||
|
||||
Finally, serve your project:
|
||||
```
|
||||
dx serve
|
||||
```
|
||||
|
||||
By default, the CLI serves your website at [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/).
|
||||
@@ -1,23 +0,0 @@
|
||||
# Installation
|
||||
|
||||
## Install the latest development build through git
|
||||
|
||||
To get the latest bug fixes and features, you can install the development version from git.
|
||||
|
||||
```
|
||||
cargo install --git https://github.com/Dioxuslabs/cli
|
||||
```
|
||||
|
||||
This will download `Dioxus-CLI` source from GitHub master branch,
|
||||
and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
|
||||
|
||||
## Install stable through `crates.io`
|
||||
|
||||
The published version of the Dioxus CLI is updated less often, but is more stable than the git version.
|
||||
|
||||
```
|
||||
cargo install dioxus-cli --locked
|
||||
```
|
||||
|
||||
Run `dx --help` for a list of all the available commands.
|
||||
Furthermore, you can run `dx <COMMAND> --help` to get help with a specific command.
|
||||
@@ -1,18 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
The 📦✨ **Dioxus CLI** is a tool to help get Dioxus projects off the ground.
|
||||
|
||||
## Features
|
||||
|
||||
* Build and pack a Dioxus project
|
||||
* `html` to `rsx` conversion tool
|
||||
* Hot Reload for `web` platform
|
||||
* Create a Dioxus project from `git` repo
|
||||
* And more!
|
||||
<!-- Checkmarks don't render on the website, so I've just made a normal list. You can uncomment this if the website rendering is fixed.
|
||||
- [x] `html` to `rsx` conversion tool
|
||||
- [x] Hot Reload for `web` platform
|
||||
- [x] Create a Dioxus project from `git` repo
|
||||
- [x] Build & pack Dioxus project
|
||||
- [ ] Automatically format Dioxus `rsx` code
|
||||
-->
|
||||
@@ -1,140 +0,0 @@
|
||||
# CLI Plugin development
|
||||
|
||||
**IMPORTANT: Ignore this documentation. Plugins are yet to be released and chances are it won't work for you. This is just what plugins *could* look like.**
|
||||
|
||||
In the past we used `dx tool` to use and install tools, but it was a flawed system.
|
||||
Tools were hard-coded by us, but people want more tools than we could code, so this plugin system was made to let
|
||||
anyone develop plugins and use them in Dioxus projects.
|
||||
|
||||
Plugin resources:
|
||||
* [Source code](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli/src/plugin)
|
||||
* [Unofficial Dioxus plugin community](https://github.com/DioxusPluginCommunity). Contains certain plugins you can use right now.
|
||||
|
||||
### Why Lua?
|
||||
|
||||
We chose Lua `5.4` to be the plugin developing language,
|
||||
because it's extremely lightweight, embeddable and easy to learn.
|
||||
We installed Lua into the CLI, so you don't need to do it yourself.
|
||||
|
||||
Lua resources:
|
||||
* [Official website](https://www.lua.org/). You can basically find everything here.
|
||||
* [Awesome Lua](https://github.com/LewisJEllis/awesome-lua). Additional resources (such as Lua plugins for your favorite IDE), and other *awesome* tools!
|
||||
|
||||
|
||||
## Creating a plugin
|
||||
|
||||
A plugin is just an `init.lua` file.
|
||||
You can include other files using `dofile(path)`.
|
||||
You need to have a plugin and a manager instance, which you can get using `require`:
|
||||
```lua
|
||||
local plugin = require("plugin")
|
||||
local manager = require("manager")
|
||||
```
|
||||
|
||||
You need to set some `manager` fields and then initialize the plugin:
|
||||
```lua
|
||||
manager.name = "My first plugin"
|
||||
manager.repository = "https://github.com/john-doe/my-first-plugin" -- The repository URL.
|
||||
manager.author = "John Doe <john.doe@example.com>"
|
||||
manager.version = "0.1.0"
|
||||
plugin.init(manager)
|
||||
```
|
||||
|
||||
You also need to return the `manager`, which basically represents your plugin:
|
||||
```lua
|
||||
-- Your code here.
|
||||
-- End of file.
|
||||
|
||||
manager.serve.interval = 1000
|
||||
return manager
|
||||
```
|
||||
|
||||
And you're ready to go. Now, go and have a look at the stuff below and the API documentation.
|
||||
|
||||
### Plugin info
|
||||
|
||||
You will encounter this type in the events below. The keys are as follows:
|
||||
* `name: string` - The name of the plugin.
|
||||
* `repository: string` - The plugin repository URL.
|
||||
* `author: string` - The author of the plugin.
|
||||
* `version: string` - The plugin version.
|
||||
|
||||
### Event management
|
||||
|
||||
The plugin library has certain events that you can subscribe to.
|
||||
|
||||
* `manager.on_init` - Triggers the first time the plugin is loaded.
|
||||
* `manager.build.on_start(info)` - Triggers before the build process. E.g., before `dx build`.
|
||||
* `manager.build.on_finish(info)` - Triggers after the build process. E.g., after `dx build`.
|
||||
* `manager.serve.on_start(info)` - Triggers before the serving process. E.g., before `dx serve`.
|
||||
* `manager.serve.on_rebuild_start(info)` - Triggers before the server rebuilds the web with hot reload.
|
||||
* `manager.serve.on_rebuild_end(info)` - Triggers after the server rebuilds the web with hot reload.
|
||||
* `manager.serve.on_shutdown` - Triggers when the server is shutdown. E.g., when the `dx serve` process is terminated.
|
||||
|
||||
To subscribe to an event, you simply need to assign it to a function:
|
||||
|
||||
```lua
|
||||
manager.build.on_start = function (info)
|
||||
log.info("[plugin] Build starting: " .. info.name)
|
||||
end
|
||||
```
|
||||
|
||||
### Plugin template
|
||||
|
||||
```lua
|
||||
package.path = library_dir .. "/?.lua"
|
||||
|
||||
local plugin = require("plugin")
|
||||
local manager = require("manager")
|
||||
|
||||
-- deconstruct api functions
|
||||
local log = plugin.log
|
||||
|
||||
-- plugin information
|
||||
manager.name = "Hello Dixous Plugin"
|
||||
manager.repository = "https://github.com/mrxiaozhuox/hello-dioxus-plugin"
|
||||
manager.author = "YuKun Liu <mrxzx.info@gmail.com>"
|
||||
manager.version = "0.0.1"
|
||||
|
||||
-- init manager info to plugin api
|
||||
plugin.init(manager)
|
||||
|
||||
manager.on_init = function ()
|
||||
-- when the first time plugin been load, this function will be execute.
|
||||
-- system will create a `dcp.json` file to verify init state.
|
||||
log.info("[plugin] Start to init plugin: " .. manager.name)
|
||||
end
|
||||
|
||||
---@param info BuildInfo
|
||||
manager.build.on_start = function (info)
|
||||
-- before the build work start, system will execute this function.
|
||||
log.info("[plugin] Build starting: " .. info.name)
|
||||
end
|
||||
|
||||
---@param info BuildInfo
|
||||
manager.build.on_finish = function (info)
|
||||
-- when the build work is done, system will execute this function.
|
||||
log.info("[plugin] Build finished: " .. info.name)
|
||||
end
|
||||
|
||||
---@param info ServeStartInfo
|
||||
manager.serve.on_start = function (info)
|
||||
-- this function will after clean & print to run, so you can print some thing.
|
||||
log.info("[plugin] Serve start: " .. info.name)
|
||||
end
|
||||
|
||||
---@param info ServeRebuildInfo
|
||||
manager.serve.on_rebuild = function (info)
|
||||
-- this function will after clean & print to run, so you can print some thing.
|
||||
local files = plugin.tool.dump(info.changed_files)
|
||||
log.info("[plugin] Serve rebuild: '" .. files .. "'")
|
||||
end
|
||||
|
||||
manager.serve.on_shutdown = function ()
|
||||
log.info("[plugin] Serve shutdown")
|
||||
end
|
||||
|
||||
manager.serve.interval = 1000
|
||||
|
||||
return manager
|
||||
```
|
||||
@@ -1,21 +0,0 @@
|
||||
# Command Functions
|
||||
|
||||
You can use command functions to execute code and scripts.
|
||||
|
||||
Type definition:
|
||||
```
|
||||
Stdio: "Inherit" | "Piped" | "Null"
|
||||
```
|
||||
|
||||
### `exec(commands: [string], stdout: Stdio, stderr: Stdio)`
|
||||
|
||||
You can use this function to run some commands on the current system.
|
||||
|
||||
```lua
|
||||
local cmd = plugin.command
|
||||
|
||||
manager.test = function ()
|
||||
cmd.exec({"git", "clone", "https://github.com/DioxusLabs/cli-plugin-library"})
|
||||
end
|
||||
```
|
||||
> Warning: This function doesn't catch exceptions.
|
||||
@@ -1,30 +0,0 @@
|
||||
# Dirs Functions
|
||||
|
||||
Dirs functions are for getting various directory paths. Not to be confused with `plugin.path`.
|
||||
|
||||
### `plugin_dir() -> string`
|
||||
|
||||
Get the plugin's root directory path.
|
||||
|
||||
```lua
|
||||
local path = plugin.dirs.plugin_dir()
|
||||
-- example: ~/Development/DioxusCli/plugin/test-plugin/
|
||||
```
|
||||
|
||||
### `bin_dir() -> string`
|
||||
|
||||
Get the plugin's binary directory path. Put binary files like `tailwind-cli` or `sass-cli` in this directory.
|
||||
|
||||
```lua
|
||||
local path = plugin.dirs.bin_dir()
|
||||
-- example: ~/Development/DioxusCli/plugin/test-plugin/bin/
|
||||
```
|
||||
|
||||
### `temp_dir() -> string`
|
||||
|
||||
Get the plugin's temporary directory path. Put any temporary files here.
|
||||
|
||||
```lua
|
||||
local path = plugin.dirs.bin_dir()
|
||||
-- example: ~/Development/DioxusCli/plugin/test-plugin/temp/
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
# Log Functions
|
||||
|
||||
You can use log functions to print various logging information.
|
||||
|
||||
### `trace(info: string)`
|
||||
|
||||
Print trace log info.
|
||||
|
||||
```lua
|
||||
local log = plugin.log
|
||||
log.trace("trace information")
|
||||
```
|
||||
|
||||
### `debug(info: string)`
|
||||
|
||||
Print debug log info.
|
||||
|
||||
```lua
|
||||
local log = plugin.log
|
||||
log.debug("debug information")
|
||||
```
|
||||
|
||||
### `info(info: string)`
|
||||
|
||||
Print info log info.
|
||||
|
||||
```lua
|
||||
local log = plugin.log
|
||||
log.info("info information")
|
||||
```
|
||||
|
||||
### `warn(info: string)`
|
||||
|
||||
Print warning log info.
|
||||
|
||||
```lua
|
||||
local log = plugin.log
|
||||
log.warn("warn information")
|
||||
```
|
||||
|
||||
### `error(info: string)`
|
||||
|
||||
Print error log info.
|
||||
|
||||
```lua
|
||||
local log = plugin.log
|
||||
log.error("error information")
|
||||
```
|
||||
@@ -1,37 +0,0 @@
|
||||
# Network Functions
|
||||
|
||||
You can use Network functions to download & read some data from the internet.
|
||||
|
||||
### `download_file(url: string, path: string) -> boolean`
|
||||
|
||||
Downloads a file from the specified URL,
|
||||
and returns a `boolean` that represents the download status (true: success, false: failure).
|
||||
|
||||
You need to pass a target URL and a local path (where you want to save this file).
|
||||
|
||||
```lua
|
||||
-- this file will download to plugin temp directory
|
||||
local status = plugin.network.download_file(
|
||||
"http://xxx.com/xxx.zip",
|
||||
plugin.dirs.temp_dir()
|
||||
)
|
||||
if status != true then
|
||||
log.error("Download Failed")
|
||||
end
|
||||
```
|
||||
|
||||
### `clone_repo(url: string, path: string) -> boolean`
|
||||
|
||||
Clone a repository from the given URL into the given path.
|
||||
Returns a `boolean` that represents the clone status (true: success, false: failure).
|
||||
The system executing this function must have git installed.
|
||||
|
||||
```lua
|
||||
local status = plugin.network.clone_repo(
|
||||
"http://github.com/mrxiaozhuox/dioxus-starter",
|
||||
plugin.dirs.bin_dir()
|
||||
)
|
||||
if status != true then
|
||||
log.error("Clone Failed")
|
||||
end
|
||||
```
|
||||
@@ -1,11 +0,0 @@
|
||||
# OS Functions
|
||||
|
||||
OS functions are for getting system information.
|
||||
|
||||
### `current_platform() -> string ("windows" | "macos" | "linux")`
|
||||
|
||||
Get the current OS platform.
|
||||
|
||||
```lua
|
||||
local platform = plugin.os.current_platform()
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
# Path Functions
|
||||
|
||||
You can use path functions to perform operations on valid path strings.
|
||||
|
||||
### `join(path: string, extra: string) -> string`
|
||||
|
||||
<!-- TODO: Add specifics.
|
||||
From the example given, it seems like it just creates a subdirectory path.
|
||||
What would it do when "extending" file paths? -->
|
||||
Extend a path; you can extend both directory and file paths.
|
||||
|
||||
```lua
|
||||
local current_path = "~/hello/dioxus"
|
||||
local new_path = plugin.path.join(current_path, "world")
|
||||
-- new_path = "~/hello/dioxus/world"
|
||||
```
|
||||
|
||||
### `parent(path: string) -> string`
|
||||
|
||||
Return the parent path of the specified path. The parent path is always a directory.
|
||||
|
||||
```lua
|
||||
local current_path = "~/hello/dioxus"
|
||||
local new_path = plugin.path.parent(current_path)
|
||||
-- new_path = "~/hello/"
|
||||
```
|
||||
|
||||
### `exists(path: string) -> boolean`
|
||||
|
||||
Check if the specified path exists, as either a file or a directory.
|
||||
|
||||
### `is_file(path: string) -> boolean`
|
||||
|
||||
Check if the specified path is a file.
|
||||
|
||||
### `is_dir(path: string) -> boolean`
|
||||
|
||||
Check if the specified path is a directory.
|
||||
@@ -1,18 +0,0 @@
|
||||
local Api = require("./interface")
|
||||
local log = Api.log;
|
||||
|
||||
local manager = {
|
||||
name = "Dioxus-CLI Plugin Demo",
|
||||
repository = "http://github.com/DioxusLabs/cli",
|
||||
author = "YuKun Liu <mrxzx.info@gmail.com>",
|
||||
}
|
||||
|
||||
manager.onLoad = function ()
|
||||
log.info("plugin loaded.")
|
||||
end
|
||||
|
||||
manager.onStartBuild = function ()
|
||||
log.warn("system start to build")
|
||||
end
|
||||
|
||||
return manager
|
||||
@@ -1,25 +0,0 @@
|
||||
local interface = {}
|
||||
|
||||
if plugin_logger ~= nil then
|
||||
interface.log = plugin_logger
|
||||
else
|
||||
interface.log = {
|
||||
trace = function (info)
|
||||
print("trace: " .. info)
|
||||
end,
|
||||
debug = function (info)
|
||||
print("debug: " .. info)
|
||||
end,
|
||||
info = function (info)
|
||||
print("info: " .. info)
|
||||
end,
|
||||
warn = function (info)
|
||||
print("warn: " .. info)
|
||||
end,
|
||||
error = function (info)
|
||||
print("error: " .. info)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return interface
|
||||
@@ -23,7 +23,7 @@ title = "Dioxus | An elegant GUI library for Rust"
|
||||
|
||||
index_on_404 = true
|
||||
|
||||
watch_path = ["src"]
|
||||
watch_path = ["src", "examples"]
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
@@ -254,6 +254,7 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
|
||||
let mut cmd = subprocess::Exec::cmd("cargo")
|
||||
.cwd(&config.crate_dir)
|
||||
.arg("build")
|
||||
.arg("--quiet")
|
||||
.arg("--message-format=json");
|
||||
|
||||
if config.release {
|
||||
@@ -312,7 +313,7 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
|
||||
if !config.out_dir.is_dir() {
|
||||
create_dir_all(&config.out_dir)?;
|
||||
}
|
||||
copy(res_path, &config.out_dir.join(target_file))?;
|
||||
copy(res_path, config.out_dir.join(target_file))?;
|
||||
|
||||
// this code will copy all public file to the output dir
|
||||
if config.asset_dir.is_dir() {
|
||||
@@ -436,14 +437,14 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String {
|
||||
String::from(include_str!("./assets/index.html"))
|
||||
};
|
||||
|
||||
let resouces = config.web.resource.clone();
|
||||
let resources = config.web.resource.clone();
|
||||
|
||||
let mut style_list = resouces.style.unwrap_or_default();
|
||||
let mut script_list = resouces.script.unwrap_or_default();
|
||||
let mut style_list = resources.style.unwrap_or_default();
|
||||
let mut script_list = resources.script.unwrap_or_default();
|
||||
|
||||
if serve {
|
||||
let mut dev_style = resouces.dev.style.clone();
|
||||
let mut dev_script = resouces.dev.script;
|
||||
let mut dev_script = resouces.dev.script.clone();
|
||||
style_list.append(&mut dev_style);
|
||||
script_list.append(&mut dev_script);
|
||||
}
|
||||
@@ -671,35 +672,3 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// use binary_install::{Cache, Download};
|
||||
|
||||
// /// Attempts to find `wasm-opt` in `PATH` locally, or failing that downloads a
|
||||
// /// precompiled binary.
|
||||
// ///
|
||||
// /// Returns `Some` if a binary was found or it was successfully downloaded.
|
||||
// /// Returns `None` if a binary wasn't found in `PATH` and this platform doesn't
|
||||
// /// have precompiled binaries. Returns an error if we failed to download the
|
||||
// /// binary.
|
||||
// pub fn find_wasm_opt(
|
||||
// cache: &Cache,
|
||||
// install_permitted: bool,
|
||||
// ) -> Result<install::Status, failure::Error> {
|
||||
// // First attempt to look up in PATH. If found assume it works.
|
||||
// if let Ok(path) = which::which("wasm-opt") {
|
||||
// PBAR.info(&format!("found wasm-opt at {:?}", path));
|
||||
|
||||
// match path.as_path().parent() {
|
||||
// Some(path) => return Ok(install::Status::Found(Download::at(path))),
|
||||
// None => {}
|
||||
// }
|
||||
// }
|
||||
|
||||
// let version = "version_78";
|
||||
// Ok(install::download_prebuilt(
|
||||
// &install::Tool::WasmOpt,
|
||||
// cache,
|
||||
// version,
|
||||
// install_permitted,
|
||||
// )?)
|
||||
// }
|
||||
@@ -46,18 +46,28 @@ impl Autoformat {
|
||||
|
||||
// Format single file
|
||||
if let Some(file) = self.file {
|
||||
let file_content = fs::read_to_string(&file);
|
||||
let file_content = if file == "-" {
|
||||
let mut contents = String::new();
|
||||
std::io::stdin().read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
} else {
|
||||
fs::read_to_string(&file)
|
||||
};
|
||||
|
||||
match file_content {
|
||||
Ok(s) => {
|
||||
let edits = dioxus_autofmt::fmt_file(&s);
|
||||
let out = dioxus_autofmt::apply_formats(&s, edits);
|
||||
match fs::write(&file, out) {
|
||||
Ok(_) => {
|
||||
println!("formatted {}", file);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("failed to write formatted content to file: {}", e);
|
||||
if file == "-" {
|
||||
print!("{}", out);
|
||||
} else {
|
||||
match fs::write(&file, out) {
|
||||
Ok(_) => {
|
||||
println!("formatted {}", file);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("failed to write formatted content to file: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,10 +132,10 @@ impl Bundle {
|
||||
.project_out_directory(crate_config.out_dir)
|
||||
.package_settings(PackageSettings {
|
||||
product_name: crate_config.dioxus_config.application.name.clone(),
|
||||
version: package.version,
|
||||
description: package.description.unwrap_or_default(),
|
||||
homepage: package.homepage,
|
||||
authors: Some(package.authors),
|
||||
version: package.version().to_string(),
|
||||
description: package.description().unwrap_or_default().to_string(),
|
||||
homepage: Some(package.homepage().unwrap_or_default().to_string()),
|
||||
authors: Some(Vec::from(package.authors())),
|
||||
default_run: Some(crate_config.dioxus_config.application.name.clone()),
|
||||
})
|
||||
.binaries(binaries)
|
||||
|
||||
@@ -216,7 +216,7 @@ pub struct WebWatcherConfig {
|
||||
}
|
||||
|
||||
fn watch_path_default() -> Vec<PathBuf> {
|
||||
vec![PathBuf::from("src")]
|
||||
vec![PathBuf::from("src"), PathBuf::from("examples")]
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
pub const DIOXUS_CLI_VERSION: &str = "0.4.1";
|
||||
|
||||
pub mod builder;
|
||||
|
||||
@@ -124,27 +124,34 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()
|
||||
let _ = local_socket_stream.set_nonblocking(true);
|
||||
move || {
|
||||
loop {
|
||||
if let Ok(mut connection) = local_socket_stream.accept() {
|
||||
// send any templates than have changed before the socket connected
|
||||
let templates: Vec<_> = {
|
||||
file_map
|
||||
.lock()
|
||||
.unwrap()
|
||||
.map
|
||||
.values()
|
||||
.filter_map(|(_, template_slot)| *template_slot)
|
||||
.collect()
|
||||
};
|
||||
for template in templates {
|
||||
if !send_msg(
|
||||
HotReloadMsg::UpdateTemplate(template),
|
||||
&mut connection,
|
||||
) {
|
||||
continue;
|
||||
match local_socket_stream.accept() {
|
||||
Ok(mut connection) => {
|
||||
// send any templates than have changed before the socket connected
|
||||
let templates: Vec<_> = {
|
||||
file_map
|
||||
.lock()
|
||||
.unwrap()
|
||||
.map
|
||||
.values()
|
||||
.filter_map(|(_, template_slot)| *template_slot)
|
||||
.collect()
|
||||
};
|
||||
for template in templates {
|
||||
if !send_msg(
|
||||
HotReloadMsg::UpdateTemplate(template),
|
||||
&mut connection,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
channels.lock().unwrap().push(connection);
|
||||
println!("Connected to hot reloading 🚀");
|
||||
}
|
||||
Err(err) => {
|
||||
if err.kind() != std::io::ErrorKind::WouldBlock {
|
||||
println!("Error connecting to hot reloading: {} (Hot reloading is a feature of the dioxus-cli. If you are not using the CLI, this error can be ignored)", err);
|
||||
}
|
||||
}
|
||||
channels.lock().unwrap().push(connection);
|
||||
println!("Connected to hot reloading 🚀");
|
||||
}
|
||||
if *aborted.lock().unwrap() {
|
||||
break;
|
||||
|
||||
@@ -112,12 +112,12 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
|
||||
.unwrap();
|
||||
|
||||
for sub_path in allow_watch_path {
|
||||
watcher
|
||||
.watch(
|
||||
&config.crate_dir.join(sub_path),
|
||||
notify::RecursiveMode::Recursive,
|
||||
)
|
||||
.unwrap();
|
||||
if let Err(err) = watcher.watch(
|
||||
&config.crate_dir.join(sub_path),
|
||||
notify::RecursiveMode::Recursive,
|
||||
) {
|
||||
log::error!("Failed to watch path: {}", err);
|
||||
}
|
||||
}
|
||||
Ok(watcher)
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ pub fn print_console_info(
|
||||
log::warn!(
|
||||
"{}",
|
||||
format!(
|
||||
"There were {} warning messages during the build.",
|
||||
"There were {} warning messages during the build. Run `cargo check` to see them.",
|
||||
options.warnings.len() - 1
|
||||
)
|
||||
.yellow()
|
||||
|
||||
@@ -19,6 +19,7 @@ syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
dioxus-rsx = { workspace = true }
|
||||
dioxus-core = { workspace = true }
|
||||
constcat = "0.3.0"
|
||||
prettyplease = "0.2.15"
|
||||
|
||||
# testing
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -23,9 +23,12 @@
|
||||
|
||||
`dioxus-core-macro` provides a handful of helpful macros used by the `dioxus` crate. These include:
|
||||
|
||||
- The `rsx!` macro that underpins templates and node creation
|
||||
- The `inline_props` macro transforms function arguments into an auto-derived struct
|
||||
- The `format_args_f` macro which allows f-string formatting with support for expressions
|
||||
- The `rsx!` macro that underpins templates and node creation.
|
||||
- The `component` attribute macro denotes a function as a Dioxus component. Currently, this:
|
||||
- Transforms function arguments into an auto-derived struct.
|
||||
- Ensures that your component name uses PascalCase.
|
||||
- Probably more stuff in the future. This macro allows us to have a way of distinguishing functions and components, which can be quite handy.
|
||||
- The `format_args_f` macro which allows f-string formatting with support for expressions.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ fn get_out_comp_fn(orig_comp_fn: &ItemFn, cx_pat: &Pat) -> ItemFn {
|
||||
block: parse_quote! {
|
||||
{
|
||||
#[warn(non_snake_case)]
|
||||
#[allow(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
#inner_comp_fn
|
||||
#inner_comp_ident (#cx_pat)
|
||||
|
||||
@@ -30,166 +30,312 @@ impl ToTokens for InlinePropsDeserializerOutput {
|
||||
impl DeserializerArgs<InlinePropsDeserializerOutput> for InlinePropsDeserializerArgs {
|
||||
fn to_output(&self, component_body: &ComponentBody) -> Result<InlinePropsDeserializerOutput> {
|
||||
Ok(InlinePropsDeserializerOutput {
|
||||
comp_fn: Self::get_function(component_body),
|
||||
props_struct: Self::get_props_struct(component_body),
|
||||
comp_fn: get_function(component_body),
|
||||
props_struct: get_props_struct(component_body),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InlinePropsDeserializerArgs {
|
||||
fn get_props_struct(component_body: &ComponentBody) -> ItemStruct {
|
||||
let ComponentBody { item_fn, .. } = component_body;
|
||||
let ItemFn { vis, sig, .. } = item_fn;
|
||||
let Signature {
|
||||
inputs,
|
||||
ident: fn_ident,
|
||||
generics,
|
||||
..
|
||||
} = sig;
|
||||
fn get_props_struct(component_body: &ComponentBody) -> ItemStruct {
|
||||
let ComponentBody { item_fn, .. } = component_body;
|
||||
let ItemFn { vis, sig, .. } = item_fn;
|
||||
let Signature {
|
||||
inputs,
|
||||
ident: fn_ident,
|
||||
generics,
|
||||
..
|
||||
} = sig;
|
||||
|
||||
// Skip first arg since that's the context
|
||||
let struct_fields = inputs.iter().skip(1).map(move |f| {
|
||||
match f {
|
||||
FnArg::Receiver(_) => unreachable!(), // Unreachable because of ComponentBody parsing
|
||||
FnArg::Typed(pt) => {
|
||||
let arg_pat = &pt.pat; // Pattern (identifier)
|
||||
let arg_colon = &pt.colon_token;
|
||||
let arg_ty = &pt.ty; // Type
|
||||
let arg_attrs = &pt.attrs; // Attributes
|
||||
// Skip first arg since that's the context
|
||||
let struct_fields = inputs.iter().skip(1).map(move |f| {
|
||||
match f {
|
||||
FnArg::Receiver(_) => unreachable!(), // Unreachable because of ComponentBody parsing
|
||||
FnArg::Typed(pt) => {
|
||||
let arg_pat = &pt.pat; // Pattern (identifier)
|
||||
let arg_colon = &pt.colon_token;
|
||||
let arg_ty = &pt.ty; // Type
|
||||
let arg_attrs = &pt.attrs; // Attributes
|
||||
|
||||
quote! {
|
||||
#(#arg_attrs)
|
||||
*
|
||||
#vis #arg_pat #arg_colon #arg_ty
|
||||
}
|
||||
quote! {
|
||||
#(#arg_attrs)
|
||||
*
|
||||
#vis #arg_pat #arg_colon #arg_ty
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
|
||||
|
||||
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
|
||||
Some(lt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let struct_attrs = if first_lifetime.is_some() {
|
||||
quote! { #[derive(Props)] }
|
||||
} else {
|
||||
quote! { #[derive(Props, PartialEq)] }
|
||||
};
|
||||
|
||||
let struct_generics = if first_lifetime.is_some() {
|
||||
let struct_generics: Punctuated<GenericParam, Comma> = component_body
|
||||
.item_fn
|
||||
.sig
|
||||
.generics
|
||||
.params
|
||||
.iter()
|
||||
.map(|it| match it {
|
||||
GenericParam::Type(tp) => {
|
||||
let mut tp = tp.clone();
|
||||
tp.bounds.push(parse_quote!( 'a ));
|
||||
|
||||
GenericParam::Type(tp)
|
||||
}
|
||||
_ => it.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote! { <#struct_generics> }
|
||||
} else {
|
||||
quote! { #generics }
|
||||
};
|
||||
|
||||
parse_quote! {
|
||||
#struct_attrs
|
||||
#[allow(non_camel_case_types)]
|
||||
#vis struct #struct_ident #struct_generics
|
||||
{
|
||||
#(#struct_fields),*
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fn get_function(component_body: &ComponentBody) -> ItemFn {
|
||||
let ComponentBody {
|
||||
item_fn,
|
||||
cx_pat_type,
|
||||
..
|
||||
} = component_body;
|
||||
let ItemFn {
|
||||
attrs: fn_attrs,
|
||||
vis,
|
||||
sig,
|
||||
block: fn_block,
|
||||
} = item_fn;
|
||||
let Signature {
|
||||
inputs,
|
||||
ident: fn_ident,
|
||||
generics,
|
||||
output: fn_output,
|
||||
asyncness,
|
||||
..
|
||||
} = sig;
|
||||
let Generics { where_clause, .. } = generics;
|
||||
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
|
||||
|
||||
let cx_pat = &cx_pat_type.pat;
|
||||
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
|
||||
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
|
||||
Some(lt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Skip first arg since that's the context
|
||||
let struct_field_names = inputs.iter().skip(1).filter_map(|f| match f {
|
||||
FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
|
||||
FnArg::Typed(t) => Some(&t.pat),
|
||||
});
|
||||
let struct_attrs = if first_lifetime.is_some() {
|
||||
quote! { #[derive(Props)] }
|
||||
} else {
|
||||
quote! { #[derive(Props, PartialEq)] }
|
||||
};
|
||||
|
||||
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
|
||||
Some(lt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let struct_generics = if first_lifetime.is_some() {
|
||||
let struct_generics: Punctuated<GenericParam, Comma> = component_body
|
||||
.item_fn
|
||||
.sig
|
||||
.generics
|
||||
.params
|
||||
.iter()
|
||||
.map(|it| match it {
|
||||
GenericParam::Type(tp) => {
|
||||
let mut tp = tp.clone();
|
||||
tp.bounds.push(parse_quote!( 'a ));
|
||||
|
||||
let (scope_lifetime, fn_generics) = if let Some(lt) = first_lifetime {
|
||||
(quote! { #lt, }, generics.clone())
|
||||
} else {
|
||||
let lifetime: LifetimeParam = parse_quote! { 'a };
|
||||
GenericParam::Type(tp)
|
||||
}
|
||||
_ => it.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut fn_generics = generics.clone();
|
||||
fn_generics
|
||||
.params
|
||||
.insert(0, GenericParam::Lifetime(lifetime.clone()));
|
||||
quote! { <#struct_generics> }
|
||||
} else {
|
||||
quote! { #generics }
|
||||
};
|
||||
|
||||
(quote! { #lifetime, }, fn_generics)
|
||||
};
|
||||
|
||||
let generics_no_bounds = {
|
||||
let mut generics = generics.clone();
|
||||
generics.params = generics
|
||||
.params
|
||||
.iter()
|
||||
.map(|it| match it {
|
||||
GenericParam::Type(tp) => {
|
||||
let mut tp = tp.clone();
|
||||
tp.bounds.clear();
|
||||
|
||||
GenericParam::Type(tp)
|
||||
}
|
||||
_ => it.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
generics
|
||||
};
|
||||
|
||||
parse_quote! {
|
||||
#(#fn_attrs)*
|
||||
#asyncness #vis fn #fn_ident #fn_generics (#cx_pat: Scope<#scope_lifetime #struct_ident #generics_no_bounds>) #fn_output
|
||||
#where_clause
|
||||
{
|
||||
let #struct_ident { #(#struct_field_names),* } = &#cx_pat.props;
|
||||
#fn_block
|
||||
}
|
||||
parse_quote! {
|
||||
#struct_attrs
|
||||
#[allow(non_camel_case_types)]
|
||||
#vis struct #struct_ident #struct_generics
|
||||
{
|
||||
#(#struct_fields),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_props_docs(fn_ident: &Ident, inputs: Vec<&FnArg>) -> Vec<Attribute> {
|
||||
if inputs.len() <= 1 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let arg_docs = inputs
|
||||
.iter()
|
||||
.filter_map(|f| match f {
|
||||
FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
|
||||
FnArg::Typed(pt) => {
|
||||
let arg_doc = pt
|
||||
.attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
// TODO: Error reporting
|
||||
// Check if the path of the attribute is "doc"
|
||||
if !is_attr_doc(attr) {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Meta::NameValue(meta_name_value) = &attr.meta else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Expr::Lit(doc_lit) = &meta_name_value.value else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Lit::Str(doc_lit_str) = &doc_lit.lit else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(doc_lit_str.value())
|
||||
})
|
||||
.fold(String::new(), |mut doc, next_doc_line| {
|
||||
doc.push('\n');
|
||||
doc.push_str(&next_doc_line);
|
||||
doc
|
||||
});
|
||||
|
||||
Some((
|
||||
&pt.pat,
|
||||
&pt.ty,
|
||||
pt.attrs.iter().find_map(|attr| {
|
||||
if attr.path() != &parse_quote!(deprecated) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let res = crate::utils::DeprecatedAttribute::from_meta(&attr.meta);
|
||||
|
||||
match res {
|
||||
Err(e) => panic!("{}", e.to_string()),
|
||||
Ok(v) => Some(v),
|
||||
}
|
||||
}),
|
||||
arg_doc,
|
||||
))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut props_docs = Vec::with_capacity(5);
|
||||
let props_def_link = fn_ident.to_string() + "Props";
|
||||
let header =
|
||||
format!("# Props\n*For details, see the [props struct definition]({props_def_link}).*");
|
||||
|
||||
props_docs.push(parse_quote! {
|
||||
#[doc = #header]
|
||||
});
|
||||
|
||||
for (arg_name, arg_type, deprecation, input_arg_doc) in arg_docs {
|
||||
let arg_name = arg_name.into_token_stream().to_string();
|
||||
let arg_type = crate::utils::format_type_string(arg_type);
|
||||
|
||||
let input_arg_doc = keep_up_to_n_consecutive_chars(input_arg_doc.trim(), 2, '\n')
|
||||
.replace("\n\n", "</p><p>");
|
||||
let prop_def_link = format!("{props_def_link}::{arg_name}");
|
||||
let mut arg_doc = format!("- [`{arg_name}`]({prop_def_link}) : `{arg_type}`");
|
||||
|
||||
if let Some(deprecation) = deprecation {
|
||||
arg_doc.push_str("<p>👎 Deprecated");
|
||||
|
||||
if let Some(since) = deprecation.since {
|
||||
arg_doc.push_str(&format!(" since {since}"));
|
||||
}
|
||||
|
||||
if let Some(note) = deprecation.note {
|
||||
let note = keep_up_to_n_consecutive_chars(¬e, 1, '\n').replace('\n', " ");
|
||||
let note = keep_up_to_n_consecutive_chars(¬e, 1, '\t').replace('\t', " ");
|
||||
|
||||
arg_doc.push_str(&format!(": {note}"));
|
||||
}
|
||||
|
||||
arg_doc.push_str("</p>");
|
||||
|
||||
if !input_arg_doc.is_empty() {
|
||||
arg_doc.push_str("<hr/>");
|
||||
}
|
||||
}
|
||||
|
||||
if !input_arg_doc.is_empty() {
|
||||
arg_doc.push_str(&format!("<p>{input_arg_doc}</p>"));
|
||||
}
|
||||
|
||||
props_docs.push(parse_quote! {
|
||||
#[doc = #arg_doc]
|
||||
});
|
||||
}
|
||||
|
||||
props_docs
|
||||
}
|
||||
|
||||
fn get_function(component_body: &ComponentBody) -> ItemFn {
|
||||
let ComponentBody {
|
||||
item_fn,
|
||||
cx_pat_type,
|
||||
..
|
||||
} = component_body;
|
||||
let ItemFn {
|
||||
attrs: fn_attrs,
|
||||
vis,
|
||||
sig,
|
||||
block: fn_block,
|
||||
} = item_fn;
|
||||
let Signature {
|
||||
inputs,
|
||||
ident: fn_ident,
|
||||
generics,
|
||||
output: fn_output,
|
||||
asyncness,
|
||||
..
|
||||
} = sig;
|
||||
let Generics { where_clause, .. } = generics;
|
||||
|
||||
let cx_pat = &cx_pat_type.pat;
|
||||
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
|
||||
|
||||
// Skip first arg since that's the context
|
||||
let struct_field_names = inputs.iter().skip(1).filter_map(|f| match f {
|
||||
FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
|
||||
FnArg::Typed(pt) => Some(&pt.pat),
|
||||
});
|
||||
|
||||
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
|
||||
Some(lt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (scope_lifetime, fn_generics) = if let Some(lt) = first_lifetime {
|
||||
(quote! { #lt, }, generics.clone())
|
||||
} else {
|
||||
let lifetime: LifetimeParam = parse_quote! { 'a };
|
||||
|
||||
let mut fn_generics = generics.clone();
|
||||
fn_generics
|
||||
.params
|
||||
.insert(0, GenericParam::Lifetime(lifetime.clone()));
|
||||
|
||||
(quote! { #lifetime, }, fn_generics)
|
||||
};
|
||||
|
||||
let generics_no_bounds = {
|
||||
let mut generics = generics.clone();
|
||||
generics.params = generics
|
||||
.params
|
||||
.iter()
|
||||
.map(|it| match it {
|
||||
GenericParam::Type(tp) => {
|
||||
let mut tp = tp.clone();
|
||||
tp.bounds.clear();
|
||||
|
||||
GenericParam::Type(tp)
|
||||
}
|
||||
_ => it.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
generics
|
||||
};
|
||||
|
||||
let props_docs = get_props_docs(fn_ident, inputs.iter().skip(1).collect());
|
||||
|
||||
parse_quote! {
|
||||
#(#fn_attrs)*
|
||||
#(#props_docs)*
|
||||
#asyncness #vis fn #fn_ident #fn_generics (#cx_pat: Scope<#scope_lifetime #struct_ident #generics_no_bounds>) #fn_output
|
||||
#where_clause
|
||||
{
|
||||
let #struct_ident { #(#struct_field_names),* } = &#cx_pat.props;
|
||||
#fn_block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the attribute is a `#[doc]` attribute.
|
||||
fn is_attr_doc(attr: &Attribute) -> bool {
|
||||
attr.path() == &parse_quote!(doc)
|
||||
}
|
||||
|
||||
fn keep_up_to_n_consecutive_chars(
|
||||
input: &str,
|
||||
n_of_consecutive_chars_allowed: usize,
|
||||
target_char: char,
|
||||
) -> String {
|
||||
let mut output = String::new();
|
||||
let mut prev_char: Option<char> = None;
|
||||
let mut consecutive_count = 0;
|
||||
|
||||
for c in input.chars() {
|
||||
match prev_char {
|
||||
Some(prev) if c == target_char && prev == target_char => {
|
||||
if consecutive_count < n_of_consecutive_chars_allowed {
|
||||
output.push(c);
|
||||
consecutive_count += 1;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
output.push(c);
|
||||
prev_char = Some(c);
|
||||
consecutive_count = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use rsx::RenderCallBody;
|
||||
@@ -8,6 +12,7 @@ use syn::{parse_macro_input, Path, Token};
|
||||
mod component_body;
|
||||
mod component_body_deserializers;
|
||||
mod props;
|
||||
mod utils;
|
||||
|
||||
// mod rsx;
|
||||
use crate::component_body::ComponentBody;
|
||||
|
||||
@@ -551,18 +551,16 @@ mod struct_info {
|
||||
let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| {
|
||||
args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into()));
|
||||
});
|
||||
let phantom_generics = self.generics.params.iter().map(|param| match param {
|
||||
let phantom_generics = self.generics.params.iter().filter_map(|param| match param {
|
||||
syn::GenericParam::Lifetime(lifetime) => {
|
||||
let lifetime = &lifetime.lifetime;
|
||||
quote!(::core::marker::PhantomData<&#lifetime ()>)
|
||||
Some(quote!(::core::marker::PhantomData<&#lifetime ()>))
|
||||
}
|
||||
syn::GenericParam::Type(ty) => {
|
||||
let ty = &ty.ident;
|
||||
quote!(::core::marker::PhantomData<#ty>)
|
||||
}
|
||||
syn::GenericParam::Const(_cnst) => {
|
||||
quote!()
|
||||
Some(quote!(::core::marker::PhantomData<#ty>))
|
||||
}
|
||||
syn::GenericParam::Const(_cnst) => None,
|
||||
});
|
||||
let builder_method_doc = match self.builder_attr.builder_method_doc {
|
||||
Some(ref doc) => quote!(#doc),
|
||||
@@ -633,7 +631,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
Ok(quote! {
|
||||
impl #impl_generics #name #ty_generics #where_clause {
|
||||
#[doc = #builder_method_doc]
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, clippy::type_complexity)]
|
||||
#vis fn builder() -> #builder_name #generics_with_empty {
|
||||
#builder_name {
|
||||
fields: #empties_tuple,
|
||||
@@ -701,6 +699,14 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
}
|
||||
|
||||
pub fn field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
|
||||
let FieldInfo {
|
||||
name: field_name,
|
||||
ty: field_type,
|
||||
..
|
||||
} = field;
|
||||
if *field_name == "key" {
|
||||
return Err(Error::new_spanned(field_name, "Naming a prop `key` is not allowed because the name can conflict with the built in key attribute. See https://dioxuslabs.com/learn/0.4/reference/dynamic_rendering#rendering-lists for more information about keys"));
|
||||
}
|
||||
let StructInfo {
|
||||
ref builder_name, ..
|
||||
} = *self;
|
||||
@@ -715,11 +721,6 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
});
|
||||
let reconstructing = self.included_fields().map(|f| f.name);
|
||||
|
||||
let FieldInfo {
|
||||
name: field_name,
|
||||
ty: field_type,
|
||||
..
|
||||
} = field;
|
||||
let mut ty_generics: Vec<syn::GenericArgument> = self
|
||||
.generics
|
||||
.params
|
||||
@@ -822,6 +823,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||
impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
|
||||
#doc
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > {
|
||||
let #field_name = (#arg_expr,);
|
||||
let ( #(#descructuring,)* ) = self.fields;
|
||||
@@ -840,6 +842,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||
#[deprecated(
|
||||
note = #repeated_fields_error_message
|
||||
)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
use quote::ToTokens;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{parse_quote, Expr, Lit, Meta, Token, Type};
|
||||
|
||||
const FORMATTED_TYPE_START: &str = "static TY_AFTER_HERE:";
|
||||
const FORMATTED_TYPE_END: &str = "= todo!();";
|
||||
|
||||
/// Attempts to convert the given literal to a string.
|
||||
/// Converts ints and floats to their base 10 counterparts.
|
||||
///
|
||||
/// Returns `None` if the literal is [`Lit::Verbatim`] or if the literal is [`Lit::ByteStr`]
|
||||
/// and the byte string could not be converted to UTF-8.
|
||||
pub fn lit_to_string(lit: Lit) -> Option<String> {
|
||||
match lit {
|
||||
Lit::Str(l) => Some(l.value()),
|
||||
Lit::ByteStr(l) => String::from_utf8(l.value()).ok(),
|
||||
Lit::Byte(l) => Some(String::from(l.value() as char)),
|
||||
Lit::Char(l) => Some(l.value().to_string()),
|
||||
Lit::Int(l) => Some(l.base10_digits().to_string()),
|
||||
Lit::Float(l) => Some(l.base10_digits().to_string()),
|
||||
Lit::Bool(l) => Some(l.value().to_string()),
|
||||
Lit::Verbatim(_) => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_type_string(ty: &Type) -> String {
|
||||
let ty_unformatted = ty.into_token_stream().to_string();
|
||||
let ty_unformatted = ty_unformatted.trim();
|
||||
|
||||
// This should always be valid syntax.
|
||||
// Not Rust code, but syntax, which is the only thing that `syn` cares about.
|
||||
let Ok(file_unformatted) = syn::parse_file(&format!(
|
||||
"{FORMATTED_TYPE_START}{ty_unformatted}{FORMATTED_TYPE_END}"
|
||||
)) else {
|
||||
return ty_unformatted.to_string();
|
||||
};
|
||||
|
||||
let file_formatted = prettyplease::unparse(&file_unformatted);
|
||||
|
||||
let file_trimmed = file_formatted.trim();
|
||||
let start_removed = file_trimmed.trim_start_matches(FORMATTED_TYPE_START);
|
||||
let end_removed = start_removed.trim_end_matches(FORMATTED_TYPE_END);
|
||||
let ty_formatted = end_removed.trim();
|
||||
|
||||
ty_formatted.to_string()
|
||||
}
|
||||
|
||||
/// Represents the `#[deprecated]` attribute.
|
||||
///
|
||||
/// You can use the [`DeprecatedAttribute::from_meta`] function to try to parse an attribute to this struct.
|
||||
#[derive(Default)]
|
||||
pub struct DeprecatedAttribute {
|
||||
pub since: Option<String>,
|
||||
pub note: Option<String>,
|
||||
}
|
||||
|
||||
impl DeprecatedAttribute {
|
||||
/// Returns `None` if the given attribute was not a valid form of the `#[deprecated]` attribute.
|
||||
pub fn from_meta(meta: &Meta) -> syn::Result<Self> {
|
||||
if meta.path() != &parse_quote!(deprecated) {
|
||||
return Err(syn::Error::new(
|
||||
meta.span(),
|
||||
"attribute path is not `deprecated`",
|
||||
));
|
||||
}
|
||||
|
||||
match &meta {
|
||||
Meta::Path(_) => Ok(Self::default()),
|
||||
Meta::NameValue(name_value) => {
|
||||
let Expr::Lit(expr_lit) = &name_value.value else {
|
||||
return Err(syn::Error::new(
|
||||
name_value.span(),
|
||||
"literal in `deprecated` value must be a string",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
since: None,
|
||||
note: lit_to_string(expr_lit.lit.clone()).map(|s| s.trim().to_string()),
|
||||
})
|
||||
}
|
||||
Meta::List(list) => {
|
||||
let parsed = list.parse_args::<DeprecatedAttributeArgsParser>()?;
|
||||
|
||||
Ok(Self {
|
||||
since: parsed.since.map(|s| s.trim().to_string()),
|
||||
note: parsed.note.map(|s| s.trim().to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod kw {
|
||||
use syn::custom_keyword;
|
||||
custom_keyword!(since);
|
||||
custom_keyword!(note);
|
||||
}
|
||||
|
||||
struct DeprecatedAttributeArgsParser {
|
||||
since: Option<String>,
|
||||
note: Option<String>,
|
||||
}
|
||||
|
||||
impl Parse for DeprecatedAttributeArgsParser {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let mut since: Option<String> = None;
|
||||
let mut note: Option<String> = None;
|
||||
|
||||
if input.peek(kw::since) {
|
||||
input.parse::<kw::since>()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
|
||||
since = lit_to_string(input.parse()?);
|
||||
}
|
||||
|
||||
if input.peek(Token![,]) && input.peek2(kw::note) {
|
||||
input.parse::<Token![,]>()?;
|
||||
input.parse::<kw::note>()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
|
||||
note = lit_to_string(input.parse()?);
|
||||
}
|
||||
|
||||
Ok(Self { since, note })
|
||||
}
|
||||
}
|
||||
@@ -164,17 +164,11 @@ impl VirtualDom {
|
||||
});
|
||||
|
||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
||||
let mut listeners = scope.attributes_to_drop.borrow_mut();
|
||||
let mut listeners = scope.attributes_to_drop_before_render.borrow_mut();
|
||||
listeners.drain(..).for_each(|listener| {
|
||||
let listener = unsafe { &*listener };
|
||||
match &listener.value {
|
||||
AttributeValue::Listener(l) => {
|
||||
_ = l.take();
|
||||
}
|
||||
AttributeValue::Any(a) => {
|
||||
_ = a.take();
|
||||
}
|
||||
_ => (),
|
||||
if let AttributeValue::Listener(l) = &listener.value {
|
||||
_ = l.take();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use crate::nodes::RenderReturn;
|
||||
use crate::{Attribute, AttributeValue};
|
||||
use bumpalo::Bump;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
|
||||
pub(crate) struct BumpFrame {
|
||||
pub bump: UnsafeCell<Bump>,
|
||||
pub node: Cell<*const RenderReturn<'static>>,
|
||||
pub(crate) attributes_to_drop_before_reset: RefCell<Vec<*const Attribute<'static>>>,
|
||||
}
|
||||
|
||||
impl BumpFrame {
|
||||
@@ -13,6 +16,7 @@ impl BumpFrame {
|
||||
Self {
|
||||
bump: UnsafeCell::new(bump),
|
||||
node: Cell::new(std::ptr::null()),
|
||||
attributes_to_drop_before_reset: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +35,23 @@ impl BumpFrame {
|
||||
unsafe { &*self.bump.get() }
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub(crate) unsafe fn bump_mut(&self) -> &mut Bump {
|
||||
unsafe { &mut *self.bump.get() }
|
||||
pub(crate) fn add_attribute_to_drop(&self, attribute: *const Attribute<'static>) {
|
||||
self.attributes_to_drop_before_reset
|
||||
.borrow_mut()
|
||||
.push(attribute);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn reset(&self) {
|
||||
let mut attributes = self.attributes_to_drop_before_reset.borrow_mut();
|
||||
attributes.drain(..).for_each(|attribute| {
|
||||
let attribute = unsafe { &*attribute };
|
||||
if let AttributeValue::Any(l) = &attribute.value {
|
||||
_ = l.take();
|
||||
}
|
||||
});
|
||||
unsafe {
|
||||
let bump = &mut *self.bump.get();
|
||||
bump.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod any_props;
|
||||
@@ -32,7 +34,7 @@ pub(crate) mod innerlude {
|
||||
pub use crate::nodes::RenderReturn;
|
||||
pub use crate::nodes::*;
|
||||
pub use crate::properties::*;
|
||||
pub use crate::runtime::{in_runtime, override_runtime, Runtime};
|
||||
pub use crate::runtime::{Runtime, RuntimeGuard};
|
||||
pub use crate::scheduler::*;
|
||||
pub use crate::scope_context::*;
|
||||
pub use crate::scopes::*;
|
||||
@@ -87,11 +89,11 @@ pub use crate::innerlude::{
|
||||
pub mod prelude {
|
||||
pub use crate::innerlude::{
|
||||
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
|
||||
in_runtime, override_runtime, provide_context, provide_context_to_scope,
|
||||
provide_root_context, push_future, remove_future, schedule_update_any, spawn,
|
||||
spawn_forever, suspend, throw, AnyValue, Component, Element, Event, EventHandler, Fragment,
|
||||
IntoAttributeValue, LazyNodes, Properties, Runtime, Scope, ScopeId, ScopeState, Scoped,
|
||||
TaskId, Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
|
||||
provide_context, provide_context_to_scope, provide_root_context, push_future,
|
||||
remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
|
||||
Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, IntoDynNode,
|
||||
LazyNodes, Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId,
|
||||
Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,51 +7,6 @@ thread_local! {
|
||||
static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = RefCell::new(vec![]);
|
||||
}
|
||||
|
||||
/// Run some code within a runtime
|
||||
pub fn in_runtime<R>(runtime: Rc<Runtime>, f: impl FnOnce() -> R) -> R {
|
||||
let _guard = RuntimeGuard::new(runtime);
|
||||
f()
|
||||
}
|
||||
|
||||
/// Override the current runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let virtual_dom = VirtualDom::new(app);
|
||||
/// }
|
||||
///
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// render!{ Component { runtime: Runtime::current().unwrap() } }
|
||||
/// }
|
||||
///
|
||||
/// // In a dynamic library
|
||||
/// #[derive(Props)]
|
||||
/// struct ComponentProps {
|
||||
/// runtime: std::rc::Rc<Runtime>,
|
||||
/// }
|
||||
///
|
||||
/// impl PartialEq for ComponentProps {
|
||||
/// fn eq(&self, _other: &Self) -> bool {
|
||||
/// true
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn Component(cx: Scope<ComponentProps>) -> Element {
|
||||
/// cx.use_hook(|| override_runtime(cx.props.runtime.clone()));
|
||||
///
|
||||
/// render! { div {} }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn override_runtime(runtime: Rc<Runtime>) {
|
||||
RUNTIMES.with(|stack| {
|
||||
let mut stack = stack.borrow_mut();
|
||||
stack.pop();
|
||||
stack.push(runtime);
|
||||
});
|
||||
}
|
||||
|
||||
/// Pushes a new scope onto the stack
|
||||
pub(crate) fn push_runtime(runtime: Rc<Runtime>) {
|
||||
RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
|
||||
@@ -143,10 +98,42 @@ impl Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RuntimeGuard(Rc<Runtime>);
|
||||
/// A gaurd for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let virtual_dom = VirtualDom::new(app);
|
||||
/// }
|
||||
///
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// render!{ Component { runtime: Runtime::current().unwrap() } }
|
||||
/// }
|
||||
///
|
||||
/// // In a dynamic library
|
||||
/// #[derive(Props)]
|
||||
/// struct ComponentProps {
|
||||
/// runtime: std::rc::Rc<Runtime>,
|
||||
/// }
|
||||
///
|
||||
/// impl PartialEq for ComponentProps {
|
||||
/// fn eq(&self, _other: &Self) -> bool {
|
||||
/// true
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn Component(cx: Scope<ComponentProps>) -> Element {
|
||||
/// cx.use_hook(|| RuntimeGuard::new(cx.props.runtime.clone()));
|
||||
///
|
||||
/// render! { div {} }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct RuntimeGuard(Rc<Runtime>);
|
||||
|
||||
impl RuntimeGuard {
|
||||
pub(crate) fn new(runtime: Rc<Runtime>) -> Self {
|
||||
/// Create a new runtime guard that sets the current Dioxus runtime. The runtime will be reset when the guard is dropped
|
||||
pub fn new(runtime: Rc<Runtime>) -> Self {
|
||||
push_runtime(runtime.clone());
|
||||
Self(runtime)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ impl VirtualDom {
|
||||
hook_idx: Default::default(),
|
||||
|
||||
borrowed_props: Default::default(),
|
||||
attributes_to_drop: Default::default(),
|
||||
attributes_to_drop_before_render: Default::default(),
|
||||
}));
|
||||
|
||||
let context =
|
||||
@@ -54,7 +54,7 @@ impl VirtualDom {
|
||||
|
||||
let new_nodes = unsafe {
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
scope.previous_frame().bump_mut().reset();
|
||||
scope.previous_frame().reset();
|
||||
|
||||
scope.context().suspended.set(false);
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ pub struct ScopeState {
|
||||
pub(crate) hook_idx: Cell<usize>,
|
||||
|
||||
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
|
||||
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
|
||||
pub(crate) attributes_to_drop_before_render: RefCell<Vec<*const Attribute<'static>>>,
|
||||
|
||||
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
|
||||
}
|
||||
@@ -348,13 +348,19 @@ impl<'src> ScopeState {
|
||||
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
|
||||
let element = rsx.call(self);
|
||||
|
||||
let mut listeners = self.attributes_to_drop.borrow_mut();
|
||||
let mut listeners = self.attributes_to_drop_before_render.borrow_mut();
|
||||
for attr in element.dynamic_attrs {
|
||||
match attr.value {
|
||||
AttributeValue::Any(_) | AttributeValue::Listener(_) => {
|
||||
// We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped
|
||||
AttributeValue::Listener(_) => {
|
||||
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
|
||||
listeners.push(unbounded);
|
||||
}
|
||||
// We need to drop any values manually to make sure that their drop implementation is called before the next render
|
||||
AttributeValue::Any(_) => {
|
||||
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
|
||||
self.previous_frame().add_attribute_to_drop(unbounded);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ tokio_runtime = ["tokio"]
|
||||
fullscreen = ["wry/fullscreen"]
|
||||
transparent = ["wry/transparent"]
|
||||
devtools = ["wry/devtools"]
|
||||
tray = ["wry/tray"]
|
||||
dox = ["wry/dox"]
|
||||
hot-reload = ["dioxus-hot-reload"]
|
||||
|
||||
|
||||
@@ -161,6 +161,7 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
||||
// iOS panics if we create a window before the event loop is started
|
||||
let props = Rc::new(Cell::new(Some(props)));
|
||||
let cfg = Rc::new(Cell::new(Some(cfg)));
|
||||
let mut is_visible_before_start = true;
|
||||
|
||||
event_loop.run(move |window_event, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
@@ -210,6 +211,8 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
||||
// Create a dom
|
||||
let dom = VirtualDom::new_with_props(root, props);
|
||||
|
||||
is_visible_before_start = cfg.window.window.visible;
|
||||
|
||||
let handler = create_new_window(
|
||||
cfg,
|
||||
event_loop,
|
||||
@@ -323,6 +326,10 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
||||
EventData::Ipc(msg) if msg.method() == "initialize" => {
|
||||
let view = webviews.get_mut(&event.1).unwrap();
|
||||
send_edits(view.dom.rebuild(), &view.desktop_context.webview);
|
||||
view.desktop_context
|
||||
.webview
|
||||
.window()
|
||||
.set_visible(is_visible_before_start);
|
||||
}
|
||||
|
||||
EventData::Ipc(msg) if msg.method() == "browser_open" => {
|
||||
|
||||
@@ -153,7 +153,7 @@ fn get_asset_root() -> Option<PathBuf> {
|
||||
|
||||
/// Get the mime type from a path-like string
|
||||
fn get_mime_from_path(trimmed: &Path) -> Result<&'static str> {
|
||||
if trimmed.ends_with(".svg") {
|
||||
if trimmed.extension().is_some_and(|ext| ext == "svg") {
|
||||
return Ok("image/svg+xml");
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ pub fn build(
|
||||
proxy: EventLoopProxy<UserWindowEvent>,
|
||||
) -> (WebView, WebContext) {
|
||||
let builder = cfg.window.clone();
|
||||
let window = builder.build(event_loop).unwrap();
|
||||
let window = builder.with_visible(false).build(event_loop).unwrap();
|
||||
let file_handler = cfg.file_drop_handler.take();
|
||||
let custom_head = cfg.custom_head.clone();
|
||||
let index_file = cfg.custom_index.clone();
|
||||
|
||||
@@ -10,7 +10,6 @@ keywords = ["dom", "ui", "gui", "react", "terminal"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
dioxus = { workspace = true }
|
||||
dioxus-core = { workspace = true, features = ["serialize"] }
|
||||
dioxus-html = { workspace = true }
|
||||
dioxus-native-core = { workspace = true, features = ["dioxus"] }
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
mod element;
|
||||
|
||||
use std::{
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
pub use dioxus_core as core;
|
||||
|
||||
#[cfg(feature = "hooks")]
|
||||
|
||||
@@ -28,6 +28,7 @@ impl CallbackApi {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn use_atom_context(cx: &ScopeState) -> &CallbackApi {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use std::{
|
||||
///
|
||||
///
|
||||
///
|
||||
#[must_use]
|
||||
pub fn use_atom_ref<'a, T: 'static>(
|
||||
cx: &'a ScopeState,
|
||||
atom: &'static AtomRef<T>,
|
||||
|
||||
@@ -7,6 +7,6 @@ use dioxus_core::ScopeState;
|
||||
pub fn use_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
|
||||
cx.use_hook(|| match cx.consume_context::<Rc<AtomRoot>>() {
|
||||
Some(root) => root,
|
||||
None => panic!("No atom root found in context. Did you forget place an AtomRoot component at the top of your app?"),
|
||||
None => panic!("No atom root found in context. Did you forget to call use_init_atom_root at the top of your app?"),
|
||||
})
|
||||
}
|
||||
@@ -2,10 +2,12 @@ use crate::{use_atom_root, AtomId, AtomRoot, Readable};
|
||||
use dioxus_core::{ScopeId, ScopeState};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[must_use]
|
||||
pub fn use_read<V: 'static>(cx: &ScopeState, f: impl Readable<V>) -> &V {
|
||||
use_read_rc(cx, f).as_ref()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn use_read_rc<V: 'static>(cx: &ScopeState, f: impl Readable<V>) -> &Rc<V> {
|
||||
let root = use_atom_root(cx);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::{use_atom_root, Writable};
|
||||
use dioxus_core::ScopeState;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[must_use]
|
||||
pub fn use_set<T: 'static>(cx: &ScopeState, f: impl Writable<T>) -> &Rc<dyn Fn(T)> {
|
||||
let root = use_atom_root(cx);
|
||||
cx.use_hook(|| {
|
||||
|
||||
@@ -30,6 +30,7 @@ use std::{
|
||||
/// ))
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn use_atom_state<T: 'static>(cx: &ScopeState, f: impl Writable<T>) -> &AtomState<T> {
|
||||
let root = crate::use_atom_root(cx);
|
||||
|
||||
@@ -85,7 +86,9 @@ impl<T: 'static> AtomState<T> {
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn current(&self) -> Rc<T> {
|
||||
self.value.as_ref().unwrap().clone()
|
||||
let atoms = self.root.atoms.borrow();
|
||||
let slot = atoms.get(&self.id).unwrap();
|
||||
slot.value.clone().downcast().unwrap()
|
||||
}
|
||||
|
||||
/// Get the `setter` function directly without the `AtomState` wrapper.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::*;
|
||||
|
||||
@@ -11,7 +11,7 @@ keywords = ["ui", "gui", "react", "ssr", "fullstack"]
|
||||
|
||||
[dependencies]
|
||||
# server functions
|
||||
server_fn = { version = "0.4.6", default-features = false }
|
||||
server_fn = { version = "0.5.2", default-features = false }
|
||||
dioxus_server_macro = { workspace = true }
|
||||
|
||||
# warp
|
||||
|
||||
@@ -22,6 +22,7 @@ use std::sync::Arc;
|
||||
/// will be allowed to continue
|
||||
///
|
||||
/// - dependencies: a tuple of references to values that are PartialEq + Clone
|
||||
#[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
|
||||
pub fn use_server_future<T, F, D>(
|
||||
cx: &ScopeState,
|
||||
dependencies: D,
|
||||
|
||||
@@ -121,8 +121,15 @@ impl<Props: Clone + serde::Serialize + serde::de::DeserializeOwned + Send + Sync
|
||||
#[cfg(feature = "web")]
|
||||
/// Launch the web application
|
||||
pub fn launch_web(self) {
|
||||
let cfg = self.web_cfg.hydrate(true);
|
||||
dioxus_web::launch_with_props(self.component, get_root_props_from_document().unwrap(), cfg);
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
{
|
||||
let cfg = self.web_cfg.hydrate(true);
|
||||
dioxus_web::launch_with_props(
|
||||
self.component,
|
||||
get_root_props_from_document().unwrap(),
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
|
||||
@@ -64,3 +64,10 @@ pub mod prelude {
|
||||
|
||||
pub use hooks::{server_cached::server_cached, server_future::use_server_future};
|
||||
}
|
||||
|
||||
// Warn users about overlapping features
|
||||
#[cfg(all(feature = "ssr", feature = "web"))]
|
||||
compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `web` feature are overlapping. Please choose one or the other.");
|
||||
|
||||
#[cfg(all(feature = "ssr", feature = "desktop"))]
|
||||
compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `desktop` feature are overlapping. Please choose one or the other.");
|
||||
@@ -125,14 +125,6 @@ impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
fn register(
|
||||
url: &'static str,
|
||||
server_function: ServerFunction,
|
||||
encoding: server_fn::Encoding,
|
||||
) -> Result<(), Self::Error> {
|
||||
Self::register_explicit("", url, server_function, encoding)
|
||||
}
|
||||
|
||||
/// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
|
||||
fn get(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
|
||||
REGISTERED_SERVER_FUNCTIONS
|
||||
|
||||
@@ -31,4 +31,4 @@ let store = Store::default();
|
||||
|
||||
## How it works
|
||||
|
||||
Internally
|
||||
Internally, `generational-box` creates an arena of generational RefCell's that are recyled when the owner is dropped. You can think of the cells as something like `&'static RefCell<Box<dyn Any>>` with a generational check to make recyling a cell easier to debug. Then GenerationalBox's are `Copy` because the `&'static` pointer is `Copy`
|
||||
@@ -184,7 +184,7 @@ impl<T: 'static> GenerationalBox<T> {
|
||||
}
|
||||
|
||||
/// Try to read the value. Returns None if the value is no longer valid.
|
||||
pub fn try_read(&self) -> Option<Ref<'_, T>> {
|
||||
pub fn try_read(&self) -> Option<Ref<'static, T>> {
|
||||
self.validate()
|
||||
.then(|| {
|
||||
Ref::filter_map(self.raw.data.borrow(), |any| {
|
||||
@@ -196,12 +196,12 @@ impl<T: 'static> GenerationalBox<T> {
|
||||
}
|
||||
|
||||
/// Read the value. Panics if the value is no longer valid.
|
||||
pub fn read(&self) -> Ref<'_, T> {
|
||||
pub fn read(&self) -> Ref<'static, T> {
|
||||
self.try_read().unwrap()
|
||||
}
|
||||
|
||||
/// Try to write the value. Returns None if the value is no longer valid.
|
||||
pub fn try_write(&self) -> Option<RefMut<'_, T>> {
|
||||
pub fn try_write(&self) -> Option<RefMut<'static, T>> {
|
||||
self.validate()
|
||||
.then(|| {
|
||||
RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
|
||||
@@ -213,7 +213,7 @@ impl<T: 'static> GenerationalBox<T> {
|
||||
}
|
||||
|
||||
/// Write the value. Panics if the value is no longer valid.
|
||||
pub fn write(&self) -> RefMut<'_, T> {
|
||||
pub fn write(&self) -> RefMut<'static, T> {
|
||||
self.try_write().unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ use std::{
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn use_tracked_state<T: 'static>(cx: &ScopeState, init: impl FnOnce() -> T) -> &Tracked<T> {
|
||||
cx.use_hook(|| {
|
||||
let init = init();
|
||||
@@ -160,6 +161,7 @@ impl<I> Drop for Tracker<I> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "Consider using the `use_effect` hook to rerun an effect whenever the tracked state changes if you don't need the result of the computation"]
|
||||
pub fn use_selector<I: 'static, O: Clone + PartialEq + 'static>(
|
||||
cx: &ScopeState,
|
||||
tracked: &Tracked<I>,
|
||||
|
||||
+26
-21
@@ -1,7 +1,10 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![cfg_attr(feature = "nightly-features", feature(debug_refcell))]
|
||||
|
||||
#[macro_export]
|
||||
/// A helper macro for using hooks and properties in async environements.
|
||||
/// A helper macro for using hooks and properties in async environments.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
@@ -54,35 +57,37 @@ macro_rules! to_owned {
|
||||
|
||||
pub mod computed;
|
||||
|
||||
mod use_on_unmount;
|
||||
pub use use_on_unmount::*;
|
||||
mod use_on_destroy;
|
||||
pub use use_on_destroy::*;
|
||||
|
||||
mod usecontext;
|
||||
pub use usecontext::*;
|
||||
mod use_context;
|
||||
pub use use_context::*;
|
||||
|
||||
mod usestate;
|
||||
pub use usestate::{use_state, UseState};
|
||||
mod use_state;
|
||||
pub use use_state::{use_state, UseState};
|
||||
|
||||
mod useref;
|
||||
pub use useref::*;
|
||||
mod use_ref;
|
||||
pub use use_ref::*;
|
||||
|
||||
mod use_shared_state;
|
||||
pub use use_shared_state::*;
|
||||
|
||||
mod usecoroutine;
|
||||
pub use usecoroutine::*;
|
||||
mod use_coroutine;
|
||||
pub use use_coroutine::*;
|
||||
|
||||
mod usefuture;
|
||||
pub use usefuture::*;
|
||||
mod use_future;
|
||||
pub use use_future::*;
|
||||
|
||||
mod useeffect;
|
||||
pub use useeffect::*;
|
||||
mod use_effect;
|
||||
pub use use_effect::*;
|
||||
|
||||
mod usecallback;
|
||||
pub use usecallback::*;
|
||||
mod use_callback;
|
||||
pub use use_callback::*;
|
||||
|
||||
mod usememo;
|
||||
pub use usememo::*;
|
||||
mod use_memo;
|
||||
pub use use_memo::*;
|
||||
|
||||
mod userootcontext;
|
||||
pub use userootcontext::*;
|
||||
mod use_on_create;
|
||||
pub use use_on_create::*;
|
||||
mod use_root_context;
|
||||
pub use use_root_context::*;
|
||||
@@ -24,6 +24,7 @@ macro_rules! use_callback {
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn use_callback<T, R, F>(cx: &ScopeState, make: impl FnOnce() -> R) -> impl FnMut(T) + '_
|
||||
where
|
||||
R: FnMut(T) -> F + 'static,
|
||||
File renamed without changes.
@@ -3,6 +3,7 @@ use dioxus_core::ScopeState;
|
||||
/// Consume some context in the tree, providing a sharable handle to the value
|
||||
///
|
||||
/// Does not regenerate the value if the value is changed at the parent.
|
||||
#[must_use]
|
||||
pub fn use_context<T: 'static + Clone>(cx: &ScopeState) -> Option<&T> {
|
||||
cx.use_hook(|| cx.consume_context::<T>()).as_ref()
|
||||
}
|
||||
@@ -79,6 +79,7 @@ where
|
||||
/// Get a handle to a coroutine higher in the tree
|
||||
///
|
||||
/// See the docs for [`use_coroutine`] for more details.
|
||||
#[must_use]
|
||||
pub fn use_coroutine_handle<M: 'static>(cx: &ScopeState) -> Option<&Coroutine<M>> {
|
||||
cx.use_hook(|| cx.consume_context::<Coroutine<M>>())
|
||||
.as_ref()
|
||||
@@ -3,13 +3,18 @@ use std::{any::Any, cell::Cell, future::Future};
|
||||
|
||||
use crate::UseFutureDep;
|
||||
|
||||
/// A hook that provides a future that executes after the hooks have been applied
|
||||
/// A hook that provides a future that executes after the hooks have been applied.
|
||||
///
|
||||
/// Whenever the hooks dependencies change, the future will be re-evaluated.
|
||||
/// If a future is pending when the dependencies change, the previous future
|
||||
/// will be allowed to continue
|
||||
/// will be allowed to continue.
|
||||
///
|
||||
/// - dependencies: a tuple of references to values that are `PartialEq` + `Clone`
|
||||
/// **Note:** If your dependency list is always empty, use [`use_on_create`](crate::use_on_create).
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// - `dependencies`: a tuple of references to values that are `PartialEq` + `Clone`.
|
||||
/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
File renamed without changes.
@@ -28,6 +28,7 @@ use crate::UseFutureDep;
|
||||
/// render!(Calculator { number: 0 })
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use = "Consider using `use_effect` to run rerun a callback when dependencies change"]
|
||||
pub fn use_memo<T, D>(cx: &ScopeState, dependencies: D, callback: impl FnOnce(D::Out) -> T) -> &T
|
||||
where
|
||||
T: 'static,
|
||||
File renamed without changes.
@@ -0,0 +1,27 @@
|
||||
use dioxus_core::ScopeState;
|
||||
use std::cell::Cell;
|
||||
use std::future::Future;
|
||||
|
||||
/// A hook that runs a future when the component is mounted.
|
||||
///
|
||||
/// This is just [`use_effect`](crate::use_effect), but with no dependencies.
|
||||
/// If you have no dependencies, it's recommended to use this, not just because it's more readable,
|
||||
/// but also because it's a tiny bit more efficient.
|
||||
pub fn use_on_create<T, F>(cx: &ScopeState, future: impl FnOnce() -> F)
|
||||
where
|
||||
T: 'static,
|
||||
F: Future<Output = T> + 'static,
|
||||
{
|
||||
let needs_regen = cx.use_hook(|| Cell::new(true));
|
||||
|
||||
if needs_regen.get() {
|
||||
// We don't need regen anymore
|
||||
needs_regen.set(false);
|
||||
|
||||
let fut = future();
|
||||
|
||||
cx.push_future(async move {
|
||||
fut.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,20 @@
|
||||
/// Creates a callback that will be run before the component is removed. This can be used to clean up side effects from the component (created with use_effect)
|
||||
#[deprecated(
|
||||
note = "Use `use_on_destroy` instead, which has the same functionality. \
|
||||
This is deprecated because of the introduction of `use_on_create` which is better mirrored by `use_on_destroy`. \
|
||||
The reason why `use_on_create` is not `use_on_mount` is because of potential confusion with `dioxus::events::onmounted`."
|
||||
)]
|
||||
pub fn use_on_unmount<D: FnOnce() + 'static>(cx: &dioxus_core::ScopeState, destroy: D) {
|
||||
use_on_destroy(cx, destroy);
|
||||
}
|
||||
|
||||
/// Creates a callback that will be run before the component is removed.
|
||||
/// This can be used to clean up side effects from the component
|
||||
/// (created with [`use_effect`](crate::use_effect)).
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
|
||||
///
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// let state = use_state(cx, || true);
|
||||
/// render! {
|
||||
@@ -25,7 +36,7 @@
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
|
||||
///
|
||||
/// fn child_component(cx: Scope) -> Element {
|
||||
/// let original_scroll_position = use_state(cx, || 0.0);
|
||||
/// use_effect(cx, (), move |_| {
|
||||
@@ -38,8 +49,8 @@
|
||||
/// original_scroll_position.set(window.scroll_y().unwrap());
|
||||
/// }
|
||||
/// });
|
||||
|
||||
/// use_on_unmount(cx, {
|
||||
///
|
||||
/// use_on_destroy(cx, {
|
||||
/// to_owned![original_scroll_position];
|
||||
/// /// restore scroll to the top of the page
|
||||
/// move || {
|
||||
@@ -47,7 +58,7 @@
|
||||
/// window.scroll_with_x_and_y(*original_scroll_position.current(), 0.0);
|
||||
/// }
|
||||
/// });
|
||||
|
||||
///
|
||||
/// render!{
|
||||
/// div {
|
||||
/// id: "my_element",
|
||||
@@ -56,7 +67,7 @@
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_on_unmount<D: FnOnce() + 'static>(cx: &dioxus_core::ScopeState, destroy: D) {
|
||||
pub fn use_on_destroy<D: FnOnce() + 'static>(cx: &dioxus_core::ScopeState, destroy: D) {
|
||||
cx.use_hook(|| LifeCycle {
|
||||
ondestroy: Some(destroy),
|
||||
});
|
||||
@@ -110,6 +110,7 @@ use std::{
|
||||
/// }
|
||||
/// })
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn use_ref<T: 'static>(cx: &ScopeState, initialize_refcell: impl FnOnce() -> T) -> &UseRef<T> {
|
||||
let hook = cx.use_hook(|| UseRef {
|
||||
update: cx.schedule_update(),
|
||||
File renamed without changes.
@@ -158,6 +158,7 @@ impl<T> ProvidedStateInner<T> {
|
||||
/// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider.
|
||||
///
|
||||
/// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified.
|
||||
#[must_use]
|
||||
pub fn use_shared_state<T: 'static>(cx: &ScopeState) -> Option<&UseSharedState<T>> {
|
||||
let state_owner: &mut Option<UseSharedStateOwner<T>> = &mut *cx.use_hook(move || {
|
||||
let scope_id = cx.scope_id();
|
||||
|
||||
File renamed without changes.
@@ -30,6 +30,7 @@ use std::{
|
||||
/// ))
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn use_state<T: 'static>(
|
||||
cx: &ScopeState,
|
||||
initial_state_fn: impl FnOnce() -> T,
|
||||
@@ -336,6 +337,50 @@ impl<T> PartialEq<UseState<T>> for UseState<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::cmp::PartialOrd> PartialOrd<T> for UseState<T> {
|
||||
fn ge(&self, other: &T) -> bool {
|
||||
*self.current_val >= *other
|
||||
}
|
||||
|
||||
fn gt(&self, other: &T) -> bool {
|
||||
*self.current_val > *other
|
||||
}
|
||||
|
||||
fn le(&self, other: &T) -> bool {
|
||||
*self.current_val <= *other
|
||||
}
|
||||
|
||||
fn lt(&self, other: &T) -> bool {
|
||||
*self.current_val < *other
|
||||
}
|
||||
|
||||
fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {
|
||||
(*self.current_val).partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::cmp::PartialOrd> PartialOrd<UseState<T>> for UseState<T> {
|
||||
fn ge(&self, other: &UseState<T>) -> bool {
|
||||
self.current_val >= other.current_val
|
||||
}
|
||||
|
||||
fn gt(&self, other: &UseState<T>) -> bool {
|
||||
self.current_val > other.current_val
|
||||
}
|
||||
|
||||
fn le(&self, other: &UseState<T>) -> bool {
|
||||
self.current_val <= other.current_val
|
||||
}
|
||||
|
||||
fn lt(&self, other: &UseState<T>) -> bool {
|
||||
self.current_val < other.current_val
|
||||
}
|
||||
|
||||
fn partial_cmp(&self, other: &UseState<T>) -> Option<std::cmp::Ordering> {
|
||||
self.current_val.partial_cmp(&other.current_val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for UseState<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.current_val)
|
||||
Whitespace-only changes.
@@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "hot-reloading"]
|
||||
[dependencies]
|
||||
dioxus-rsx = { workspace = true }
|
||||
dioxus-core = { workspace = true, features = ["serialize"] }
|
||||
dioxus-html = { workspace = true }
|
||||
dioxus-html = { workspace = true, optional = true }
|
||||
|
||||
interprocess-docfix = { version = "1.2.2" }
|
||||
notify = { version = "5.0.0", optional = true }
|
||||
@@ -24,5 +24,6 @@ once_cell = { version = "1.17.0", optional = true }
|
||||
ignore = { version = "0.4.19", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
file_watcher = ["ignore", "chrono", "notify", "execute", "once_cell", "ignore", "dioxus-html/hot-reload-context"]
|
||||
default = ["dioxus-html"]
|
||||
custom_file_watcher = ["ignore", "chrono", "notify", "execute", "once_cell", "ignore"]
|
||||
file_watcher = ["custom_file_watcher", "dioxus-html/hot-reload-context"]
|
||||
@@ -13,11 +13,12 @@ use dioxus_rsx::{
|
||||
};
|
||||
use interprocess_docfix::local_socket::{LocalSocketListener, LocalSocketStream};
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
||||
pub use dioxus_html::HtmlCtx;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub struct Config<Ctx: HotReloadingContext = HtmlCtx> {
|
||||
#[cfg(feature = "file_watcher")]
|
||||
use dioxus_html::HtmlCtx;
|
||||
|
||||
pub struct Config<Ctx: HotReloadingContext> {
|
||||
root_path: &'static str,
|
||||
listening_paths: &'static [&'static str],
|
||||
excluded_paths: &'static [&'static str],
|
||||
@@ -39,6 +40,7 @@ impl<Ctx: HotReloadingContext> Default for Config<Ctx> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "file_watcher")]
|
||||
impl Config<HtmlCtx> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -6,9 +6,9 @@ pub use dioxus_html::HtmlCtx;
|
||||
use interprocess_docfix::local_socket::LocalSocketStream;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "file_watcher")]
|
||||
#[cfg(feature = "custom_file_watcher")]
|
||||
mod file_watcher;
|
||||
#[cfg(feature = "file_watcher")]
|
||||
#[cfg(feature = "custom_file_watcher")]
|
||||
pub use file_watcher::*;
|
||||
|
||||
/// A message the hot reloading server sends to the client
|
||||
|
||||
@@ -1107,6 +1107,7 @@ builder_constructors! {
|
||||
formnovalidate: Bool DEFAULT,
|
||||
formtarget: Target DEFAULT,
|
||||
height: isize DEFAULT,
|
||||
initial_checked: Bool DEFAULT,
|
||||
list: Id DEFAULT,
|
||||
max: String DEFAULT,
|
||||
maxlength: usize DEFAULT,
|
||||
@@ -1203,6 +1204,7 @@ builder_constructors! {
|
||||
value: String DEFAULT,
|
||||
|
||||
selected: Bool volatile,
|
||||
initial_selected: Bool DEFAULT,
|
||||
};
|
||||
|
||||
/// Build a
|
||||
|
||||
@@ -33,6 +33,7 @@ type EvalCreator = Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>;
|
||||
/// parts is practically asking for a hacker to find an XSS vulnerability in
|
||||
/// it. **This applies especially to web targets, where the JavaScript context
|
||||
/// has access to most, if not all of your application data.**
|
||||
#[must_use]
|
||||
pub fn use_eval(cx: &ScopeState) -> &EvalCreator {
|
||||
&*cx.use_hook(|| {
|
||||
let eval_provider = cx
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
//! # Dioxus Namespace for HTML
|
||||
|
||||
@@ -51,9 +51,15 @@ export function setAttributeInner(node, field, value, ns) {
|
||||
case "checked":
|
||||
node.checked = truthy(value);
|
||||
break;
|
||||
case "initial_checked":
|
||||
node.defaultChecked = truthy(value);
|
||||
break;
|
||||
case "selected":
|
||||
node.selected = truthy(value);
|
||||
break;
|
||||
case "initial_selected":
|
||||
node.defaultSelected = truthy(value);
|
||||
break;
|
||||
case "dangerous_inner_html":
|
||||
node.innerHTML = value;
|
||||
break;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
|
||||
pub static COMMON_JS: &str = include_str!("./common.js");
|
||||
|
||||
|
||||
@@ -80,9 +80,15 @@ mod js {
|
||||
case "checked":
|
||||
node.checked = truthy(value);
|
||||
break;
|
||||
case "initial_checked":
|
||||
node.defaultChecked = truthy(value);
|
||||
break;
|
||||
case "selected":
|
||||
node.selected = truthy(value);
|
||||
break;
|
||||
case "initial_selected":
|
||||
node.defaultSelected = truthy(value);
|
||||
break;
|
||||
case "dangerous_inner_html":
|
||||
node.innerHTML = value;
|
||||
break;
|
||||
|
||||
@@ -19,32 +19,43 @@ async fn main() {
|
||||
let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();
|
||||
|
||||
let view = dioxus_liveview::LiveViewPool::new();
|
||||
let index_page_with_glue = |glue: &str| {
|
||||
Html(format!(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head> <title>Dioxus LiveView with axum</title> </head>
|
||||
<body> <div id="main"></div> </body>
|
||||
{glue}
|
||||
</html>
|
||||
"#,
|
||||
))
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/",
|
||||
get(move || async move {
|
||||
Html(format!(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head> <title>Dioxus LiveView with axum</title> </head>
|
||||
<body> <div id="main"></div> </body>
|
||||
{glue}
|
||||
</html>
|
||||
"#,
|
||||
glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws"))
|
||||
))
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/ws",
|
||||
get(move |ws: WebSocketUpgrade| async move {
|
||||
ws.on_upgrade(move |socket| async move {
|
||||
_ = view.launch(dioxus_liveview::axum_socket(socket), app).await;
|
||||
})
|
||||
}),
|
||||
);
|
||||
let app =
|
||||
Router::new()
|
||||
.route(
|
||||
"/",
|
||||
get(move || async move {
|
||||
index_page_with_glue(&dioxus_liveview::interpreter_glue(&format!(
|
||||
"ws://{addr}/ws"
|
||||
)))
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/as-path",
|
||||
get(move || async move {
|
||||
index_page_with_glue(&dioxus_liveview::interpreter_glue("/ws"))
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/ws",
|
||||
get(move |ws: WebSocketUpgrade| async move {
|
||||
ws.on_upgrade(move |socket| async move {
|
||||
_ = view.launch(dioxus_liveview::axum_socket(socket), app).await;
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
println!("Listening on http://{addr}");
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
pub mod adapters {
|
||||
#[cfg(feature = "warp")]
|
||||
pub mod warp_adapter;
|
||||
@@ -93,14 +97,49 @@ static MAIN_JS: &str = include_str!("./main.js");
|
||||
/// This script that gets injected into your app connects this page to the websocket endpoint
|
||||
///
|
||||
/// Once the endpoint is connected, it will send the initial state of the app, and then start
|
||||
/// processing user events and returning edits to the liveview instance
|
||||
pub fn interpreter_glue(url: &str) -> String {
|
||||
/// processing user events and returning edits to the liveview instance.
|
||||
///
|
||||
/// You can pass a relative path prefixed with "/", or enter a full URL including the protocol
|
||||
/// (`ws:` or `wss:`) as an argument.
|
||||
///
|
||||
/// If you enter a relative path, the web client automatically prefixes the host address in
|
||||
/// `window.location` when creating a web socket to LiveView.
|
||||
///
|
||||
/// ```
|
||||
/// // Creates websocket connection to same host as current page
|
||||
/// interpreter_glue("/api/liveview");
|
||||
///
|
||||
/// // Creates websocket connection to specified url
|
||||
/// interpreter_glue("ws://localhost:8080/api/liveview");
|
||||
/// ```
|
||||
pub fn interpreter_glue(url_or_path: &str) -> String {
|
||||
// If the url starts with a `/`, generate glue which reuses current host
|
||||
let get_ws_url = if url_or_path.starts_with('/') {
|
||||
r#"
|
||||
let loc = window.location;
|
||||
let new_url = "";
|
||||
if (loc.protocol === "https:") {{
|
||||
new_url = "wss:";
|
||||
}} else {{
|
||||
new_url = "ws:";
|
||||
}}
|
||||
new_url += "//" + loc.host + path;
|
||||
return new_url;
|
||||
"#
|
||||
} else {
|
||||
"return path;"
|
||||
};
|
||||
|
||||
let js = &*INTERPRETER_JS;
|
||||
let common = &*COMMON_JS;
|
||||
format!(
|
||||
r#"
|
||||
<script>
|
||||
var WS_ADDR = "{url}";
|
||||
function __dioxusGetWsUrl(path) {{
|
||||
{get_ws_url}
|
||||
}}
|
||||
|
||||
var WS_ADDR = __dioxusGetWsUrl("{url_or_path}");
|
||||
{js}
|
||||
{common}
|
||||
{MAIN_JS}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
pub use dioxus_desktop::*;
|
||||
@@ -1,3 +1,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
Loaded 100 of 159 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user