mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add dashboard demo to the showcase
This commit is contained in:
parent
625fcf5231
commit
47ba9a13df
@ -8,3 +8,4 @@ yew = { path = "../.." }
|
||||
counter = { path = "sub/counter" }
|
||||
crm = { path = "sub/crm" }
|
||||
custom_components = { path = "sub/custom_components" }
|
||||
dashboard = { path = "sub/dashboard" }
|
||||
|
||||
@ -3,19 +3,25 @@ extern crate yew;
|
||||
extern crate counter;
|
||||
extern crate crm;
|
||||
extern crate custom_components;
|
||||
extern crate dashboard;
|
||||
|
||||
use yew::prelude::*;
|
||||
use yew::services::console::ConsoleService;
|
||||
use yew::services::dialog::DialogService;
|
||||
use yew::services::storage::{StorageService, Area};
|
||||
use yew::services::fetch::FetchService;
|
||||
use yew::services::websocket::WebSocketService;
|
||||
use counter::Model as Counter;
|
||||
use crm::Model as Crm;
|
||||
use custom_components::Model as CustomComponents;
|
||||
use dashboard::Model as Dashboard;
|
||||
|
||||
struct Context {
|
||||
console: ConsoleService,
|
||||
storage: StorageService,
|
||||
dialog: DialogService,
|
||||
web: FetchService,
|
||||
ws: WebSocketService,
|
||||
}
|
||||
|
||||
impl AsMut<ConsoleService> for Context {
|
||||
@ -36,6 +42,18 @@ impl AsMut<DialogService> for Context {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<FetchService> for Context {
|
||||
fn as_mut(&mut self) -> &mut FetchService {
|
||||
&mut self.web
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<WebSocketService> for Context {
|
||||
fn as_mut(&mut self) -> &mut WebSocketService {
|
||||
&mut self.ws
|
||||
}
|
||||
}
|
||||
|
||||
impl custom_components::Printer for Context {
|
||||
fn print(&mut self, data: &str) {
|
||||
self.console.log(data);
|
||||
@ -47,6 +65,7 @@ enum Scene {
|
||||
Counter,
|
||||
Crm,
|
||||
CustomComponents,
|
||||
Dashboard,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
@ -79,6 +98,7 @@ impl Renderable<Context, Scene> for Scene {
|
||||
<button onclick=|_| Msg::SwitchTo(Scene::Counter),>{ "Counter" }</button>
|
||||
<button onclick=|_| Msg::SwitchTo(Scene::Crm),>{ "Crm" }</button>
|
||||
<button onclick=|_| Msg::SwitchTo(Scene::CustomComponents),>{ "CustomComponents" }</button>
|
||||
<button onclick=|_| Msg::SwitchTo(Scene::Dashboard),>{ "Dashboard" }</button>
|
||||
{ self.view_scene() }
|
||||
}
|
||||
}
|
||||
@ -107,6 +127,11 @@ impl Scene {
|
||||
<CustomComponents: />
|
||||
}
|
||||
}
|
||||
Scene::Dashboard => {
|
||||
html! {
|
||||
<Dashboard: />
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,6 +142,8 @@ fn main() {
|
||||
console: ConsoleService,
|
||||
storage: StorageService::new(Area::Local),
|
||||
dialog: DialogService,
|
||||
web: FetchService::new(),
|
||||
ws: WebSocketService::new(),
|
||||
};
|
||||
let app: App<_, Scene> = App::new(context);
|
||||
app.mount_to_body();
|
||||
|
||||
@ -7,4 +7,4 @@ authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
|
||||
failure = "0.1"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
yew = { path = "../../.." }
|
||||
yew = { path = "../../../.." }
|
||||
|
||||
176
examples/showcase/sub/dashboard/src/lib.rs
Normal file
176
examples/showcase/sub/dashboard/src/lib.rs
Normal file
@ -0,0 +1,176 @@
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate yew;
|
||||
|
||||
use failure::Error;
|
||||
use yew::prelude::*;
|
||||
use yew::format::{Nothing, Json};
|
||||
use yew::services::Task;
|
||||
use yew::services::fetch::{FetchService, FetchTask, Request, Response};
|
||||
use yew::services::websocket::{WebSocketService, WebSocketTask, WebSocketStatus};
|
||||
|
||||
pub struct Model {
|
||||
fetching: bool,
|
||||
data: Option<u32>,
|
||||
ft: Option<FetchTask>,
|
||||
ws: Option<WebSocketTask>,
|
||||
}
|
||||
|
||||
pub enum WsAction {
|
||||
Connect,
|
||||
SendData,
|
||||
Disconnect,
|
||||
Lost,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
FetchData,
|
||||
WsAction(WsAction),
|
||||
FetchReady(Result<DataFromFile, Error>),
|
||||
WsReady(Result<WsResponse, Error>),
|
||||
Ignore,
|
||||
}
|
||||
|
||||
impl From<WsAction> for Msg {
|
||||
fn from(action: WsAction) -> Self {
|
||||
Msg::WsAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
/// This type is used to parse data from `./static/data.json` file and
|
||||
/// have to correspond the data layout from that file.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct DataFromFile {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
/// This type is used as a request which sent to websocket connection.
|
||||
#[derive(Serialize, Debug)]
|
||||
struct WsRequest {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
/// This type is an expected response from a websocket connection.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct WsResponse {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
impl<CTX> Component<CTX> for Model
|
||||
where
|
||||
CTX: AsMut<FetchService> + AsMut<WebSocketService> + 'static,
|
||||
{
|
||||
type Msg = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, _: &mut Env<CTX, Self>) -> Self {
|
||||
Model {
|
||||
fetching: false,
|
||||
data: None,
|
||||
ft: None,
|
||||
ws: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Msg, context: &mut Env<CTX, Self>) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::FetchData => {
|
||||
self.fetching = true;
|
||||
let callback = context.send_back(|response: Response<Json<Result<DataFromFile, Error>>>| {
|
||||
let (meta, Json(data)) = response.into_parts();
|
||||
println!("META: {:?}, {:?}", meta, data);
|
||||
if meta.status.is_success() {
|
||||
Msg::FetchReady(data)
|
||||
} else {
|
||||
Msg::Ignore // FIXME: Handle this error accordingly.
|
||||
}
|
||||
});
|
||||
let request = Request::get("/data.json").body(Nothing).unwrap();
|
||||
let fetch_service: &mut FetchService = context.as_mut();
|
||||
let task = fetch_service.fetch(request, callback);
|
||||
self.ft = Some(task);
|
||||
}
|
||||
Msg::WsAction(action) => {
|
||||
match action {
|
||||
WsAction::Connect => {
|
||||
let callback = context.send_back(|Json(data)| Msg::WsReady(data));
|
||||
let notification = context.send_back(|status| {
|
||||
match status {
|
||||
WebSocketStatus::Opened => Msg::Ignore,
|
||||
WebSocketStatus::Closed => WsAction::Lost.into(),
|
||||
}
|
||||
});
|
||||
let ws_service: &mut WebSocketService = context.as_mut();
|
||||
let task = ws_service.connect("ws://localhost:9001/", callback, notification);
|
||||
self.ws = Some(task);
|
||||
}
|
||||
WsAction::SendData => {
|
||||
let request = WsRequest {
|
||||
value: 321,
|
||||
};
|
||||
self.ws.as_mut().unwrap().send(Json(&request));
|
||||
}
|
||||
WsAction::Disconnect => {
|
||||
self.ws.take().unwrap().cancel();
|
||||
}
|
||||
WsAction::Lost => {
|
||||
self.ws = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Msg::FetchReady(response) => {
|
||||
self.fetching = false;
|
||||
self.data = response.map(|data| data.value).ok();
|
||||
}
|
||||
Msg::WsReady(response) => {
|
||||
self.data = response.map(|data| data.value).ok();
|
||||
}
|
||||
Msg::Ignore => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<CTX> Renderable<CTX, Model> for Model
|
||||
where
|
||||
CTX: AsMut<FetchService> + AsMut<WebSocketService> + 'static,
|
||||
{
|
||||
fn view(&self) -> Html<CTX, Self> {
|
||||
html! {
|
||||
<div>
|
||||
<nav class="menu",>
|
||||
<button onclick=|_| Msg::FetchData,>{ "Fetch Data" }</button>
|
||||
{ self.view_data() }
|
||||
<button disabled=self.ws.is_some(),
|
||||
onclick=|_| WsAction::Connect.into(),>{ "Connect To WebSocket" }</button>
|
||||
<button disabled=self.ws.is_none(),
|
||||
onclick=|_| WsAction::SendData.into(),>{ "Send To WebSocket" }</button>
|
||||
<button disabled=self.ws.is_none(),
|
||||
onclick=|_| WsAction::Disconnect.into(),>{ "Close WebSocket connection" }</button>
|
||||
</nav>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn view_data<CTX>(&self) -> Html<CTX, Model>
|
||||
where
|
||||
CTX: AsMut<FetchService> + AsMut<WebSocketService> + 'static,
|
||||
{
|
||||
if let Some(value) = self.data {
|
||||
html! {
|
||||
<p>{ value }</p>
|
||||
}
|
||||
} else {
|
||||
html! {
|
||||
<p>{ "Data hasn't fetched yet." }</p>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,171 +1,25 @@
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate yew;
|
||||
extern crate dashboard;
|
||||
|
||||
use failure::Error;
|
||||
use yew::prelude::*;
|
||||
use yew::format::{Nothing, Json};
|
||||
use yew::services::Task;
|
||||
use yew::services::fetch::{FetchService, FetchTask, Request, Response};
|
||||
use yew::services::websocket::{WebSocketService, WebSocketTask, WebSocketStatus};
|
||||
use yew::services::fetch::FetchService;
|
||||
use yew::services::websocket::WebSocketService;
|
||||
use dashboard::Model;
|
||||
|
||||
struct Context {
|
||||
web: FetchService,
|
||||
ws: WebSocketService,
|
||||
}
|
||||
|
||||
struct Model {
|
||||
fetching: bool,
|
||||
data: Option<u32>,
|
||||
ft: Option<FetchTask>,
|
||||
ws: Option<WebSocketTask>,
|
||||
}
|
||||
|
||||
enum WsAction {
|
||||
Connect,
|
||||
SendData,
|
||||
Disconnect,
|
||||
Lost,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
FetchData,
|
||||
WsAction(WsAction),
|
||||
FetchReady(Result<DataFromFile, Error>),
|
||||
WsReady(Result<WsResponse, Error>),
|
||||
Ignore,
|
||||
}
|
||||
|
||||
impl From<WsAction> for Msg {
|
||||
fn from(action: WsAction) -> Self {
|
||||
Msg::WsAction(action)
|
||||
impl AsMut<FetchService> for Context {
|
||||
fn as_mut(&mut self) -> &mut FetchService {
|
||||
&mut self.web
|
||||
}
|
||||
}
|
||||
|
||||
/// This type is used to parse data from `./static/data.json` file and
|
||||
/// have to correspond the data layout from that file.
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct DataFromFile {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
/// This type is used as a request which sent to websocket connection.
|
||||
#[derive(Serialize, Debug)]
|
||||
struct WsRequest {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
/// This type is an expected response from a websocket connection.
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct WsResponse {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
impl Component<Context> for Model {
|
||||
type Msg = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, _: &mut Env<Context, Self>) -> Self {
|
||||
Model {
|
||||
fetching: false,
|
||||
data: None,
|
||||
ft: None,
|
||||
ws: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Msg, context: &mut Env<Context, Self>) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::FetchData => {
|
||||
self.fetching = true;
|
||||
let callback = context.send_back(|response: Response<Json<Result<DataFromFile, Error>>>| {
|
||||
let (meta, Json(data)) = response.into_parts();
|
||||
println!("META: {:?}, {:?}", meta, data);
|
||||
if meta.status.is_success() {
|
||||
Msg::FetchReady(data)
|
||||
} else {
|
||||
Msg::Ignore // FIXME: Handle this error accordingly.
|
||||
}
|
||||
});
|
||||
let request = Request::get("/data.json").body(Nothing).unwrap();
|
||||
let task = context.web.fetch(request, callback);
|
||||
self.ft = Some(task);
|
||||
}
|
||||
Msg::WsAction(action) => {
|
||||
match action {
|
||||
WsAction::Connect => {
|
||||
let callback = context.send_back(|Json(data)| Msg::WsReady(data));
|
||||
let notification = context.send_back(|status| {
|
||||
match status {
|
||||
WebSocketStatus::Opened => Msg::Ignore,
|
||||
WebSocketStatus::Closed => WsAction::Lost.into(),
|
||||
}
|
||||
});
|
||||
let task = context.ws.connect("ws://localhost:9001/", callback, notification);
|
||||
self.ws = Some(task);
|
||||
}
|
||||
WsAction::SendData => {
|
||||
let request = WsRequest {
|
||||
value: 321,
|
||||
};
|
||||
self.ws.as_mut().unwrap().send(Json(&request));
|
||||
}
|
||||
WsAction::Disconnect => {
|
||||
self.ws.take().unwrap().cancel();
|
||||
}
|
||||
WsAction::Lost => {
|
||||
self.ws = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Msg::FetchReady(response) => {
|
||||
self.fetching = false;
|
||||
self.data = response.map(|data| data.value).ok();
|
||||
}
|
||||
Msg::WsReady(response) => {
|
||||
self.data = response.map(|data| data.value).ok();
|
||||
}
|
||||
Msg::Ignore => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable<Context, Model> for Model {
|
||||
fn view(&self) -> Html<Context, Self> {
|
||||
html! {
|
||||
<div>
|
||||
<nav class="menu",>
|
||||
<button onclick=|_| Msg::FetchData,>{ "Fetch Data" }</button>
|
||||
{ self.view_data() }
|
||||
<button disabled=self.ws.is_some(),
|
||||
onclick=|_| WsAction::Connect.into(),>{ "Connect To WebSocket" }</button>
|
||||
<button disabled=self.ws.is_none(),
|
||||
onclick=|_| WsAction::SendData.into(),>{ "Send To WebSocket" }</button>
|
||||
<button disabled=self.ws.is_none(),
|
||||
onclick=|_| WsAction::Disconnect.into(),>{ "Close WebSocket connection" }</button>
|
||||
</nav>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn view_data(&self) -> Html<Context, Model> {
|
||||
if let Some(value) = self.data {
|
||||
html! {
|
||||
<p>{ value }</p>
|
||||
}
|
||||
} else {
|
||||
html! {
|
||||
<p>{ "Data hasn't fetched yet." }</p>
|
||||
}
|
||||
}
|
||||
impl AsMut<WebSocketService> for Context {
|
||||
fn as_mut(&mut self) -> &mut WebSocketService {
|
||||
&mut self.ws
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +30,6 @@ fn main() {
|
||||
ws: WebSocketService::new(),
|
||||
};
|
||||
let app: App<Context, Model> = App::new(context);
|
||||
app.mount_to_body();;
|
||||
app.mount_to_body();
|
||||
yew::run_loop();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user