Skip to content

Commit

Permalink
main upload
Browse files Browse the repository at this point in the history
  • Loading branch information
smir-ant committed Jul 3, 2024
1 parent ecf6bfd commit be87910
Show file tree
Hide file tree
Showing 13 changed files with 278 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: All in One

on:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write # чтобы action мог push делать
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal # этот профиль включает как можно меньше компонентов для работы компилятора. Он устанавливает rustc (компилятор Rust), rust-std (стандартную библиотеку Rust) и cargo (систему сборки и менеджер пакетов Rust)
toolchain: nightly # ночная версия rust, а не стабильная, т.к. некоторые фишки практичнее здесь
target: wasm32-unknown-unknown # обозначает платформу, для которой вы компилируете ваш код. В данном случае, wasm32-unknown-unknown указывает на то, что мы компилируем код для WebAssembly (wasm32) без конкретной операционной системы или процессора (unknown-unknown).
override: true # следует ли перезаписать существующую установку Rust на вашей машине. установит указанный вами toolchain и target, даже если на вашей машине уже установлен другой toolchain или target

- name: Install Trunk
run: cargo install --locked trunk # установить trunk. --locked для гарантии, что Cargo будет использовать точные версии зависимостей, указанные в файле Cargo.lock.

- name: Build with Trunk
run: trunk build --release # сборка в статические файлы(html, js, wasm, favicon...)

- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4 # специально разработан для деплоя на GitHub Pages
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages # в ветку gh-pages
FOLDER: dist # из папки dist(формируется после сборки leptos)
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
/dist
23 changes: 23 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "wasm_parser"
version = "0.1.0"
edition = "2021"
authors = ["smir-ant"]

[dependencies]
leptos = { version = "0.6", features = ["csr", "nightly"] } # для создания реактивных компонентов в Rust. "csr" включает поддержку рендеринга на стороне клиента, а "nightly" активирует функции, доступные только в ночных сборках Rust.
leptos_meta = { version = "0.6", features = ["csr", "nightly"] } # предоставляет дополнительные инструменты и утилиты для работы с метаданными и мета-тегами
leptos_router = { version = "0.6", features = ["csr", "nightly"] } # предоставляет маршрутизацию для SPA (одностраничных приложений)
web-sys = "0.3.69" # web-sys предоставляет доступ к браузерным API через WebAssembly, что позволяет взаимодействовать с DOM и другими веб-функциями.
reqwasm = "0.5.0" # для выполнения HTTP-запросов в WebAssembly с использованием асинхронных функций.
# почему не reqwest например? ring является библиотекой, которая зависит от компиляции C-кода, а это не поддерживается для WebAssembly
scraper = "0.19.0" # для парсинга HTML документов и извлечения данных из них
# wasm-bindgen = "0.2.92" # не использовал |для взаимодействия между WebAssembly и JavaScript, позволяя вызывать JavaScript из Rust и наоборот
wasm-bindgen-futures = "0.4.42" # предоставляет инструменты для работы с асинхронным кодом в WebAssembly, интегрируя Future с wasm-bindgen
rand = "0.8.5" # исключительно для случайного выбора классов картинкам

[profile.release] # Этот раздел содержит настройки для сборки вашего проекта в режиме release.
opt-level = 'z' # сargo будет стараться минимизировать размер бинарного файла.
lto = true # включает оптимизацию всего программного обеспечения (Link Time Optimization, LTO)
codegen-units = 1 # вы говорите компилятору обрабатывать всю вашу программу как одну единицу кодогенерации. Это может привести к более эффективной оптимизации кода, потому что компилятор видит всю программу целиком, но это также может замедлить процесс компиляции, потому что он не может использовать параллелизм для ускорения | компромисс между скоростью компиляции и эффективностью оптимизации.
panic = "abort" # что делать при панике. abort означает, что процесс должен немедленно завершиться
2 changes: 2 additions & 0 deletions Trunk.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
public_url = "/wasm_parser/" # фикс путей для github-pages конкретно во всех dist файлах(т.к. в gh-pages мы начинаем не с "user.github.io/", а с "user.github.io/repository_name")
17 changes: 17 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<!-- +wasm: https://rustwasm.github.io/docs/wasm-bindgen/reference/weak-references.html -->
<link data-trunk rel="rust" data-wasm-opt="z" data-weak-refs />

<!-- +tailwind: https://trunkrs.dev/assets/#tailwind -->
<link data-trunk rel="tailwind-css" href="/public/tailwind.css" />

<!-- +css: https://trunkrs.dev/assets/#css -->
<link data-trunk rel="css" href="/public/styles.css" />

<!-- +favicon: https://trunkrs.dev/assets/#icon -->
<link data-trunk rel="icon" href="/public/favicon.ico" />
</head>
<body class="bg-neutral-100 dark:bg-neutral-800"></body>
</html>
Binary file added public/favicon.ico
Binary file not shown.
8 changes: 8 additions & 0 deletions public/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.counter {
margin: 20px 0px;
}

.counter > * {
margin-right: 10px;
}

33 changes: 33 additions & 0 deletions public/tailwind.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
button {
@apply p-3 rounded-lg text-black dark:text-white font-bold
bg-gradient-to-r from-teal-500 to-lime-500;
}

input {
@apply p-3 rounded-lg bg-white text-black dark:bg-black dark:text-white font-mono;
}
}

@layer components {
#images-container {
@apply grid p-3 gap-3;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-auto-rows: 150px;
}

#images-container img {
@apply block w-full h-full object-cover rounded-xl;
}

/* Стили для разных размеров изображений */
.size1 { grid-row: span 1; grid-column: span 1; }
.size2 { grid-row: span 2; grid-column: span 2; }
.size3 { grid-row: span 2; grid-column: span 1; }
.size4 { grid-row: span 1; grid-column: span 2; }

}
2 changes: 2 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"
27 changes: 27 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use leptos::*;
use leptos_meta::{provide_meta_context, Html, Meta, Title};
use leptos_router::{Router, Route, Routes};

mod pages;
use crate::pages::home::Home;


fn main() {
mount_to_body(|| {
provide_meta_context(); // контекст, который управляет различными аспектами веб-страницы, такими как стили, заголовки, мета-теги и т.д.
// “контекст” обычно относится к набору данных или состоянию, которое доступно всему приложению или определенной его части

view! {
<Html lang="en" dir="ltr" /> // ltr (слева-направо направление текста)
<Title text="WASM parser by smir-ant"/> // название страницы
<Meta charset="UTF-8"/>
<Meta name="viewport" content="width=device-width, initial-scale=1.0"/>

<Router>
<Routes>
<Route path="/*" view=Home />
</Routes>
</Router>
}
})
}
110 changes: 110 additions & 0 deletions src/pages/home.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use leptos::*; // Leptos для создания реактивных компонентов
use web_sys::console; // для логирования в консоль браузера
use reqwasm::http::Request; // для выполнения HTTP-запросов из wasm
use wasm_bindgen_futures::spawn_local; // для запуска асинхронных операций в текущем потоке
use scraper::{Html, Selector}; // для парсинга HTML
use rand::seq::SliceRandom; use rand::thread_rng; // для выбора случайного класса
use leptos::ev::SubmitEvent;

#[component]
pub fn Home() -> impl IntoView {
// Создаем сигнал для хранения запроса, введенного пользователем | несмотря на ссылку в value=""(речь про <input type=text>), сигнал, связанный с этим полем нужно тоже инициализировать по умолчанию этой же ссылкой
let (search_request, set_search_request) = create_signal("rust".to_string()); // p.s. иначе если сходу нажать на подгрузить, то компонент не тригернёт и value не будет взят для ссылки

// Создаем сигнал для хранения количества изображений, которые нужно загрузить
let (num_images, set_num_images) = create_signal(1);

// Обработчик клика по кнопке "Подгрузить"
let submit = {
let search_request = search_request.clone(); // Клонируем сигнал search_request для использования в замыкании
let num_images = num_images.clone(); // Клонируем сигнал num_images для использования в замыкании
move |e: SubmitEvent| {
e.prevent_default(); // Предотвращаем отправку формы

let url_value = search_request.get(); // Получаем текущее значение URL
let num_images_value = num_images.get(); // Получаем текущее значение количества изображений

// Логируем значения в консоль для отладки
console::log_1(&url_value.clone().into());
console::log_1(&num_images_value.to_string().into());

// Формируем URL для запроса с использованием введенного пользователем URL | CORS-ANYWHERE + UNSPLASH
let url = format!("https://cors-anywhere.herokuapp.com/https://unsplash.com/s/photos/{}", url_value);
console::log_1(&format!("Полученная ссылка для парсинга: {:?}", url).into());
// Выполнение асинхронного GET-запроса
spawn_local(async move {
// Отправляем GET-запрос и ждем ответа
match Request::get(&url).send().await {
// Если запрос выполнен успешно
Ok(response) => {
if response.ok() { // Если ответ успешный (код 200)
let body = response.text().await.unwrap(); // Получаем текст ответа
parse_html_and_log_images(&body, num_images_value); // Парсим HTML и логируем изображения
} else {
console::log_1(&"запрос не 200(OK)".into()); // Логируем ошибку, если ответ не успешный
}
}
// Если произошла ошибка при выполнении запроса
Err(err) => {
console::log_1(&format!("Ошибка: {:?}", err).into()); // Логируем ошибку
}
}
});
}
};

// Формируем HTML представление компонента
view! {
<form class="m-4 flex justify-between" on:submit=submit> // форма для валидации(max и min), чтобы не было -10 картинок, а то там работает, но не совсем так как ожидается
<a href="https://github.com/smir-ant/wasm_parser">
<img class="inline-block w-12 mr-4 dark:invert animate-pulse" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Octicons-mark-github.svg/1200px-Octicons-mark-github.svg.png" />
</a>
// Поле ввода для URL
<input class="mr-2 w-[65%]" type="text" value="rust" placeholder="что будем искать?" on:input=move |e| set_search_request(event_target_value(&e)) />
// Поле ввода для количества изображений
<input class="mr-2 w-[20%]" type="number" min="1" max="999" placeholder="кол-во изображений" on:input=move |e| set_num_images(event_target_value(&e).parse().unwrap_or(1)) />
// Кнопка для отправки запроса
<button>{"Подгрузить"}</button>
</form>
// Контейнер для изображений
<div id="images-container"></div>
}
}

// Функция для парсинга HTML и логирования изображений
fn parse_html_and_log_images(html: &str, num_images: usize) {
// Парсинг HTML с помощью библиотеки scraper
let document = Html::parse_document(html); // Создаем документ из HTML строки
let selector = Selector::parse("img[src]").unwrap(); // Создаем селектор для выбора всех тегов img с атрибутом src

let window = web_sys::window().expect("отсутствует a Window"); // Получаем объект окна браузера
let web_document = window.document().expect("отсутствует Document"); // Получаем объект документа браузера
let images_container = web_document.get_element_by_id("images-container").expect("отсутствует #images-container"); // Получаем контейнер для изображений
images_container.set_inner_html(""); // Очистка содержимого контейнера | если несколько раз был запрос
let mut will_show_count = 0;

// Перебираем найденные элементы img и добавляем их на страницу
for (i, element) in document.select(&selector).enumerate() {
if will_show_count >= num_images {
break; // Прерываем цикл, если добавлено достаточно изображений
}

if let Some(src) = element.value().attr("src") {
if src.starts_with("data:") || src.starts_with("https://images.unsplash.com/profile") || src.starts_with("https://images.unsplash.com/placeholder"){ continue; } // фильтруем лишнее
will_show_count += 1; // выше убрали мусор, и теперь вроде как норм фотка, поэтому +1
console::log_1(&src.into()); // Логируем src изображения в консоль

// Создание элемента img с помощью web_sys
let img = web_document.create_element("img").unwrap(); // Создаем <img>
img.set_attribute("src", src).unwrap(); // Устанавливаем атрибут src

// делаем ссылку, в которую будет помещена картинка
let link = web_document.create_element("a").unwrap(); // Создаем <a>
link.set_attribute("href", src).unwrap(); // Устанавливаем атрибут href в <a>
link.append_child(&img).unwrap(); // <img> идёт внутрь <a>
link.set_attribute("class", ["size1", "size2", "size3", "size4"].choose(&mut thread_rng()).unwrap()).unwrap(); // случайный класс для размера

images_container.append_child(&link).unwrap(); // вкладываем ссылку+картинку в контейнер
}
}
}
1 change: 1 addition & 0 deletions src/pages/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod home;
17 changes: 17 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: {
files: ["*.html", "src/pages/*.rs"],
},
theme: {
extend: {
colors: { // переопределяем цвета
neutral: { // группы neutral
100: '#F1F1F1',
800: '#202020'
}
}
},
},
plugins: [],
}

0 comments on commit be87910

Please sign in to comment.