prep for actual upload
This commit is contained in:
@@ -118,11 +118,11 @@ site-root = "target/site"
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "./styles/style.scss"
|
||||
style-file = "./styles/style.css"
|
||||
tailwind-input-file = "./styles/tailwind.css"
|
||||
|
||||
# The locales files
|
||||
watch-additional-files = ["locales"]
|
||||
watch-additional-files = ["locales", "styles"]
|
||||
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
assets-dir = "public"
|
||||
|
||||
BIN
airsoftfotos.db
BIN
airsoftfotos.db
Binary file not shown.
@@ -81,11 +81,11 @@ pub fn App() -> impl IntoView {
|
||||
<LoadingBarProvider>
|
||||
<ToasterProvider>
|
||||
<Router>
|
||||
<Layout>
|
||||
<div>
|
||||
<LayoutHeader attr:style="background-color: #0078ffaa; padding: 20px;">
|
||||
<NavBar user=user />
|
||||
</LayoutHeader>
|
||||
<Layout attr:style="padding: 20px;">
|
||||
<div style="padding: 20px;">
|
||||
<main>
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
// Route
|
||||
@@ -105,8 +105,8 @@ pub fn App() -> impl IntoView {
|
||||
/>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
</div>
|
||||
</Router>
|
||||
<BackTop1 />
|
||||
</ToasterProvider>
|
||||
|
||||
22
src/app/components/file_preview.rs
Normal file
22
src/app/components/file_preview.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thaw::*;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ImageUpload {
|
||||
pub name: String,
|
||||
pub blob_url: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn FilePreview(file: ImageUpload) -> impl IntoView {
|
||||
view! {
|
||||
<Flex vertical=true inline=false>
|
||||
<div style="margin: 10px;">
|
||||
<div style="align: center; height: 194px;"><img src={file.blob_url} style="max-height: 194px" /></div>
|
||||
<div style="align: center"><p>{file.name}</p></div>
|
||||
</div>
|
||||
</Flex>
|
||||
}
|
||||
}
|
||||
@@ -9,3 +9,9 @@ pub use ad::*;
|
||||
|
||||
mod category;
|
||||
pub use category::*;
|
||||
|
||||
mod file_preview;
|
||||
pub use file_preview::*;
|
||||
|
||||
mod upload;
|
||||
pub use upload::*;
|
||||
|
||||
150
src/app/components/select.css
Normal file
150
src/app/components/select.css
Normal file
@@ -0,0 +1,150 @@
|
||||
.thaw-select {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
font-family: var(--fontFamilyBase);
|
||||
}
|
||||
|
||||
.thaw-select::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
height: var(--borderRadiusMedium);
|
||||
background-image: linear-gradient(
|
||||
0deg,
|
||||
var(--colorCompoundBrandStroke) 0%,
|
||||
var(--colorCompoundBrandStroke) 50%,
|
||||
transparent 50%,
|
||||
transparent 100%
|
||||
);
|
||||
transition-delay: var(--curveAccelerateMid);
|
||||
transition-duration: var(--durationUltraFast);
|
||||
transition-property: transform;
|
||||
transform: scaleX(0);
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 var(--borderRadiusMedium) var(--borderRadiusMedium);
|
||||
}
|
||||
|
||||
.thaw-select:focus-within::after {
|
||||
transition-delay: var(--curveDecelerateMid);
|
||||
transition-duration: var(--durationNormal);
|
||||
transition-property: transform;
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.thaw-select__select {
|
||||
flex-grow: 1;
|
||||
padding-right: calc(
|
||||
var(--spacingHorizontalMNudge) + 20px + var(--spacingHorizontalXXS) +
|
||||
var(--spacingHorizontalXXS)
|
||||
);
|
||||
padding-left: calc(
|
||||
var(--spacingHorizontalMNudge) + var(--spacingHorizontalXXS)
|
||||
);
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
max-width: 100%;
|
||||
height: 32px;
|
||||
background-color: var(--colorNeutralBackground1);
|
||||
color: var(--colorNeutralForeground1);
|
||||
line-height: var(--lineHeightBase300);
|
||||
font-weight: var(--fontWeightRegular);
|
||||
font-size: var(--fontSizeBase300);
|
||||
font-family: var(--fontFamilyBase);
|
||||
border-radius: var(--borderRadiusMedium);
|
||||
border: 1px solid var(--colorNeutralStroke1);
|
||||
border-bottom-color: var(--colorNeutralStrokeAccessible);
|
||||
box-shadow: none;
|
||||
appearance: none;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thaw-select--small .thaw-select__select {
|
||||
height: 24px;
|
||||
padding-right: calc(
|
||||
var(--spacingHorizontalSNudge) + 16px + var(--spacingHorizontalXXS) +
|
||||
var(--spacingHorizontalXXS)
|
||||
);
|
||||
padding-left: calc(
|
||||
var(--spacingHorizontalSNudge) + var(--spacingHorizontalXXS)
|
||||
);
|
||||
}
|
||||
|
||||
.thaw-select--large .thaw-select__select {
|
||||
height: 40px;
|
||||
padding-right: calc(
|
||||
var(--spacingHorizontalM) + 24px + var(--spacingHorizontalSNudge) +
|
||||
var(--spacingHorizontalSNudge)
|
||||
);
|
||||
padding-left: calc(
|
||||
var(--spacingHorizontalM) + var(--spacingHorizontalSNudge)
|
||||
);
|
||||
}
|
||||
|
||||
.thaw-select__select:focus {
|
||||
outline-color: transparent;
|
||||
outline-style: solid;
|
||||
outline-width: 2px;
|
||||
}
|
||||
|
||||
.thaw-select:hover {
|
||||
border-bottom-color: var(--colorNeutralStrokeAccessible);
|
||||
border-left-color: var(--colorNeutralStroke1Hover);
|
||||
border-right-color: var(--colorNeutralStroke1Hover);
|
||||
border-top-color: var(--colorNeutralStroke1Hover);
|
||||
}
|
||||
|
||||
.thaw-select:active {
|
||||
border-bottom-color: var(--colorNeutralStrokeAccessible);
|
||||
border-left-color: var(--colorNeutralStroke1Pressed);
|
||||
border-right-color: var(--colorNeutralStroke1Pressed);
|
||||
border-top-color: var(--colorNeutralStroke1Pressed);
|
||||
}
|
||||
|
||||
.thaw-select__icon {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
right: var(--spacingHorizontalMNudge);
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
color: var(--colorNeutralStrokeAccessible);
|
||||
box-sizing: border-box;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.thaw-select--small .thaw-select__icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
right: var(--spacingHorizontalSNudge);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.thaw-select--large .thaw-select__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
right: var(--spacingHorizontalM);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.thaw-select__icon svg {
|
||||
display: block;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.thaw-select--disabled > .thaw-select__select {
|
||||
border-color: var(--colorNeutralStrokeDisabled);
|
||||
border-bottom-color: var(--colorNeutralStrokeDisabled);
|
||||
background-color: var(--colorTransparentBackground);
|
||||
color: var(--colorNeutralForegroundDisabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.thaw-select--disabled > .thaw-select__icon {
|
||||
color: var(--colorNeutralForegroundDisabled);
|
||||
}
|
||||
109
src/app/components/upload/mod.rs
Normal file
109
src/app/components/upload/mod.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
pub use web_sys::FileList;
|
||||
|
||||
use leptos::{ev, html, prelude::*};
|
||||
use thaw_utils::{ArcOneCallback, add_event_listener, class_list, mount_style};
|
||||
|
||||
#[component]
|
||||
pub fn Upload1(
|
||||
#[prop(optional, into)] class: MaybeProp<String>,
|
||||
#[prop(optional, into)] id: MaybeProp<String>,
|
||||
/// A string specifying a name for the input control.
|
||||
/// This name is submitted along with the control's value when the form data is submitted.
|
||||
#[prop(optional, into)]
|
||||
name: MaybeProp<String>,
|
||||
/// The accept type of upload.
|
||||
#[prop(optional, into)]
|
||||
accept: Signal<String>,
|
||||
/// Allow multiple files to be selected.
|
||||
#[prop(optional, into)]
|
||||
multiple: Signal<bool>,
|
||||
/// Customize upload request.
|
||||
#[prop(optional, into)]
|
||||
custom_request: Option<ArcOneCallback<FileList>>,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
mount_style("upload", include_str!("./upload.css"));
|
||||
|
||||
let input_ref = NodeRef::<html::Input>::new();
|
||||
let trigger_ref = NodeRef::<html::Div>::new();
|
||||
|
||||
Effect::new(move |_| {
|
||||
let Some(trigger_el) = trigger_ref.get() else {
|
||||
return;
|
||||
};
|
||||
let handle = add_event_listener(trigger_el, ev::click, move |_| {
|
||||
if let Some(input_ref) = input_ref.get_untracked() {
|
||||
input_ref.click();
|
||||
}
|
||||
});
|
||||
on_cleanup(move || {
|
||||
handle.remove();
|
||||
});
|
||||
});
|
||||
|
||||
let on_file_addition = move |files: FileList| {
|
||||
if let Some(custom_request) = custom_request.as_ref() {
|
||||
custom_request(files);
|
||||
}
|
||||
};
|
||||
|
||||
let on_change = {
|
||||
let on_file_addition = on_file_addition.clone();
|
||||
move |_| {
|
||||
if let Some(input_ref) = input_ref.get_untracked() {
|
||||
if let Some(files) = input_ref.files() {
|
||||
on_file_addition(files);
|
||||
}
|
||||
input_ref.set_value("");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let is_trigger_dragover = RwSignal::new(false);
|
||||
let on_trigger_drop = move |event: ev::DragEvent| {
|
||||
event.prevent_default();
|
||||
if let Some(data) = event.data_transfer()
|
||||
&& let Some(files) = data.files()
|
||||
{
|
||||
on_file_addition(files);
|
||||
}
|
||||
is_trigger_dragover.set(false);
|
||||
};
|
||||
let on_trigger_dragover = move |event: ev::DragEvent| {
|
||||
event.prevent_default();
|
||||
is_trigger_dragover.set(true);
|
||||
};
|
||||
let on_trigger_dragenter = move |event: ev::DragEvent| {
|
||||
event.prevent_default();
|
||||
};
|
||||
let on_trigger_dragleave = move |event: ev::DragEvent| {
|
||||
event.prevent_default();
|
||||
is_trigger_dragover.set(false);
|
||||
};
|
||||
|
||||
view! {
|
||||
<div
|
||||
node_ref=trigger_ref
|
||||
class=class_list![
|
||||
"thaw-upload",
|
||||
("thaw-upload--drag-over", move || is_trigger_dragover.get()),
|
||||
class
|
||||
]
|
||||
on:drop=on_trigger_drop
|
||||
on:dragover=on_trigger_dragover
|
||||
on:dragenter=on_trigger_dragenter
|
||||
on:dragleave=on_trigger_dragleave>
|
||||
{children()}
|
||||
</div>
|
||||
<input
|
||||
class="thaw-upload__input"
|
||||
id=move || id.get()
|
||||
name=move || name.get()
|
||||
node_ref=input_ref
|
||||
type="file"
|
||||
accept=move || accept.get()
|
||||
multiple=move || multiple.get()
|
||||
on:change=on_change
|
||||
/>
|
||||
}
|
||||
}
|
||||
16
src/app/components/upload/upload.css
Normal file
16
src/app/components/upload/upload.css
Normal file
@@ -0,0 +1,16 @@
|
||||
.thaw-upload__input {
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.thaw-upload {
|
||||
flex: 1;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
border: var(--strokeWidthThin) dashed var(--colorNeutralStroke1);
|
||||
}
|
||||
|
||||
.thaw-upload--drag-over {
|
||||
border: var(--strokeWidthThin) dashed var(--colorBrandForeground1);
|
||||
}
|
||||
@@ -1,20 +1,29 @@
|
||||
use crate::{
|
||||
app::pages::get_categories,
|
||||
app::{
|
||||
components::{FilePreview, ImageUpload, Upload1},
|
||||
models::Category,
|
||||
pages::get_categories,
|
||||
},
|
||||
auth::{Logout, User},
|
||||
error_template::ErrorTemplate,
|
||||
};
|
||||
use chrono::prelude::*;
|
||||
use leptos::prelude::*;
|
||||
use leptos::{logging::log, task::spawn_local};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thaw::*;
|
||||
use web_sys::js_sys::Uint8Array;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ImageUpload {
|
||||
pub name: String,
|
||||
pub blob_url: String,
|
||||
pub data: Vec<u8>,
|
||||
#[server]
|
||||
pub async fn go_upload(
|
||||
category: i64,
|
||||
date: NaiveDate,
|
||||
photo: ImageUpload,
|
||||
) -> Result<(), ServerFnError> {
|
||||
// ..
|
||||
log!("{}", category);
|
||||
log!("{}", date);
|
||||
log!("{}", photo.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[component]
|
||||
@@ -22,8 +31,6 @@ pub fn Account(
|
||||
user: Resource<Result<Option<User>, ServerFnError>>,
|
||||
action: ServerAction<Logout>,
|
||||
) -> impl IntoView {
|
||||
let toaster = ToasterInjection::expect_context();
|
||||
|
||||
let user = move || user.get().unwrap().unwrap().unwrap();
|
||||
let categories = Resource::new(move || (), move |_| get_categories());
|
||||
|
||||
@@ -48,35 +55,31 @@ pub fn Account(
|
||||
let arr: Uint8Array = Uint8Array::new(&jsval);
|
||||
let data: Vec<u8> = arr.to_vec();
|
||||
|
||||
// verify data is an image, if not, reject
|
||||
// todo: verify data is an image, if not, reject
|
||||
|
||||
files.write().push(ImageUpload {
|
||||
name: file.name(),
|
||||
blob_url,
|
||||
data,
|
||||
});
|
||||
log!("{}", file.name());
|
||||
});
|
||||
}
|
||||
|
||||
// toaster.dispatch_toast(
|
||||
// move || {
|
||||
// view! {
|
||||
// <Toast>
|
||||
// <ToastBody>
|
||||
// {format!("Number of uploaded files: {len}")}
|
||||
// </ToastBody>
|
||||
// </Toast>
|
||||
// }
|
||||
// },
|
||||
// Default::default(),
|
||||
// );
|
||||
};
|
||||
|
||||
Effect::new(move || {
|
||||
let cat = selected_category.get();
|
||||
log!("{}", cat);
|
||||
});
|
||||
let disabled = RwSignal::new(false);
|
||||
|
||||
let upload = move |_| {
|
||||
disabled.set(true);
|
||||
let category = selected_category.get().parse().unwrap();
|
||||
let date = date.get();
|
||||
let photos = files.get();
|
||||
|
||||
for file in photos {
|
||||
spawn_local(async move {
|
||||
let _ = dbg!(go_upload(category, date, file).await);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
<Flex vertical=false inline=true>
|
||||
@@ -92,13 +95,7 @@ pub fn Account(
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
// preview list of uploaded images
|
||||
// with a max amount probably and a ...
|
||||
|
||||
// dropdown for which field <- which is the category
|
||||
// date picker for which day <- which will become the album, or add to if it already exists
|
||||
|
||||
<Flex vertical=true inline=true>
|
||||
<Flex vertical=true inline=false>
|
||||
|
||||
<Transition fallback=move || view! { <p>"Loading..."</p> }>
|
||||
<ErrorBoundary fallback=|errors| {
|
||||
@@ -118,7 +115,7 @@ pub fn Account(
|
||||
} else {
|
||||
categories.into_iter().map(move |category| {
|
||||
view! {
|
||||
<option>{category.name}</option>
|
||||
<option value={category.id.to_string()}>{category.name}</option>
|
||||
}.into_any()
|
||||
})
|
||||
.collect_view()
|
||||
@@ -133,25 +130,35 @@ pub fn Account(
|
||||
</Transition>
|
||||
|
||||
<DatePicker value=date/>
|
||||
<Upload custom_request>
|
||||
<UploadDragger>"Sleep fotos naar hier om te uploaden."</UploadDragger>
|
||||
</Upload>
|
||||
|
||||
<div class="">
|
||||
<Transition fallback=move || view! { <p>"Loading..."</p> }>
|
||||
<ErrorBoundary fallback=|errors| {
|
||||
view! { <ErrorTemplate errors=errors/> }
|
||||
}>
|
||||
{ move || files().into_iter().map(move |file| {
|
||||
view! {
|
||||
<img src={file.blob_url} style="width: 100px" />
|
||||
<p>{file.name}</p>
|
||||
}.into_any()
|
||||
})
|
||||
.collect_view() }
|
||||
</ErrorBoundary>
|
||||
</Transition>
|
||||
<div style="display: flex; height: 250px; width: 100%;">
|
||||
<Upload1 custom_request multiple=RwSignal::new(true)>
|
||||
<Flex vertical=false inline=false gap=FlexGap::WH(0, 0)>
|
||||
<Transition fallback=move || view! { <p>"Loading..."</p> }>
|
||||
<ErrorBoundary fallback=|errors| {
|
||||
view! { <ErrorTemplate errors=errors/> }
|
||||
}>
|
||||
{ move || if files().is_empty() {
|
||||
view! {
|
||||
<div style="margin: auto;">"Sleep hier de fotos om te uploaden."</div>
|
||||
}.into_any()
|
||||
} else {
|
||||
files().into_iter().map(move |file| {
|
||||
view! {
|
||||
<FilePreview file />
|
||||
}.into_any()
|
||||
}).collect_view().into_any()
|
||||
}}
|
||||
</ErrorBoundary>
|
||||
</Transition>
|
||||
</Flex>
|
||||
</Upload1>
|
||||
</div>
|
||||
|
||||
<Button button_type=ButtonType::Button disabled on_click=upload>
|
||||
"Upload"
|
||||
</Button>
|
||||
|
||||
</Flex>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,22 @@ body {
|
||||
.card_preview {
|
||||
background: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.parent {
|
||||
background: skyblue;
|
||||
width: 350px;
|
||||
overflow-x: auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.parent>.content-wrapper {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.parent>.content-wrapper>.child {
|
||||
background: springgreen;
|
||||
white-space: normal;
|
||||
}
|
||||
@@ -1,2 +1,22 @@
|
||||
@import "tailwindcss" source("../src");
|
||||
|
||||
|
||||
|
||||
|
||||
.parent {
|
||||
background: skyblue;
|
||||
width: 350px;
|
||||
overflow-x: auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.parent>.content-wrapper {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.parent>.content-wrapper>.child {
|
||||
background: springgreen;
|
||||
white-space: normal;
|
||||
}
|
||||
Reference in New Issue
Block a user