diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..bac83fe --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,6 @@ +# clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work +# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html +# check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility +# we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93 +[target.wasm32-unknown-unknown] +rustflags = ["--cfg=web_sys_unstable_apis"] \ No newline at end of file diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..14acb9b --- /dev/null +++ b/.envrc @@ -0,0 +1,6 @@ +if ! has nix_direnv_version || ! nix_direnv_version 2.1.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.1.1/direnvrc" "sha256-b6qJ4r34rbE23yWjMqbmu3ia2z4b2wIlZUksBke/ol0=" +fi +use flake + + diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..c0d87ee --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,48 @@ +name: Github Pages + +# By default, runs if you push to main. keeps your deployed app in sync with main branch. +on: + push: + branches: + - master +# to only run when you do a new github release, comment out above part and uncomment the below trigger. +# on: +# release: +# types: +# - published + +permissions: + contents: write # for committing to gh-pages branch. + +jobs: + build-github-pages: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 # repo checkout + - name: Setup toolchain for wasm + run: | + rustup update stable + rustup default stable + rustup set profile minimal + rustup target add wasm32-unknown-unknown + - name: Rust Cache # cache the rust build artefacts + uses: Swatinem/rust-cache@v2 + - name: Download and install Trunk binary + run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- + - name: Build # build + # Environment $public_url resolves to the github project page. + # If using a user/organization page, remove the `${{ github.event.repository.name }}` part. + # using --public-url something will allow trunk to modify all the href paths like from favicon.ico to repo_name/favicon.ico . + # this is necessary for github pages where the site is deployed to username.github.io/repo_name and all files must be requested + # relatively as eframe_template/favicon.ico. if we skip public-url option, the href paths will instead request username.github.io/favicon.ico which + # will obviously return error 404 not found. + run: ./trunk build --release --public-url $public_url crates/opensi-editor/index.html + env: + public_url: "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}" + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: dist + # this option will not maintain any history of your previous pages deployment + # set to false if you want all page build to be committed to your gh-pages branch history + single-commit: true diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 25c2cee..a14b78a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,17 +1,173 @@ -name: Rust +on: [push, pull_request, workflow_dispatch] -on: [push] +name: CI + +env: + # --cfg=web_sys_unstable_apis is required to enable the web_sys clipboard API which egui_web uses + # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html + # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html + RUSTFLAGS: -D warnings --cfg=web_sys_unstable_apis + RUSTDOCFLAGS: -D warnings jobs: - build: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + args: --all-features + + check_wasm: + name: Check wasm32 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: wasm32-unknown-unknown + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + args: --all-features --lib --target wasm32-unknown-unknown + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev + - uses: actions-rs/cargo@v1 + with: + command: test + args: --lib + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: clippy + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings + trunk: + name: trunk runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.76.0 + target: wasm32-unknown-unknown + override: true + - name: Download and install Trunk binary + run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- + - name: Build + run: ./trunk build crates/opensi-editor/index.html + + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + TARGET: aarch64-apple-darwin + + - os: ubuntu-latest + TARGET: aarch64-unknown-linux-gnu + + - os: ubuntu-latest + TARGET: armv7-unknown-linux-gnueabihf + + - os: ubuntu-latest + TARGET: x86_64-unknown-linux-gnu + + - os: windows-latest + TARGET: x86_64-pc-windows-msvc + EXTENSION: .exe steps: - - uses: actions/checkout@v1 - - name: Install GTK - run: sudo apt-get install libgtk-3-dev + - name: Building ${{ matrix.TARGET }} + run: echo "${{ matrix.TARGET }}" + + - uses: actions/checkout@master + - name: Install build dependencies - Rustup + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain stable --profile default --target ${{ matrix.TARGET }} -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + # For linux, it's necessary to use cross from the git repository to avoid glibc problems + # Ref: https://github.com/cross-rs/cross/issues/1510 + - name: Install cross for linux + if: contains(matrix.TARGET, 'linux') + run: | + cargo install cross --git https://github.com/cross-rs/cross --rev 1b8cf50d20180c1a394099e608141480f934b7f7 + + - name: Install cross for mac and windows + if: ${{ !contains(matrix.TARGET, 'linux') }} + run: | + cargo install cross + - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + run: | + cross build --verbose --release --target=${{ matrix.TARGET }} --bin opensi-editor + + - name: Rename + run: cp target/${{ matrix.TARGET }}/release/opensi-editor${{ matrix.EXTENSION }} opensi-editor-${{ matrix.TARGET }}${{ matrix.EXTENSION }} + + - uses: actions/upload-artifact@master + with: + name: opensi-editor-${{ matrix.TARGET }}${{ matrix.EXTENSION }} + path: opensi-editor-${{ matrix.TARGET }}${{ matrix.EXTENSION }} + + - uses: svenstaro/upload-release-action@v2 + name: Upload binaries to release + if: ${{ github.event_name == 'push' }} + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: opensi-editor-${{ matrix.TARGET }}${{ matrix.EXTENSION }} + asset_name: opensi-editor-${{ matrix.TARGET }}${{ matrix.EXTENSION }} + tag: ${{ github.ref }} + prerelease: ${{ !startsWith(github.ref, 'refs/tags/') }} + overwrite: true diff --git a/.gitignore b/.gitignore index 03fa777..696be30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,11 @@ +# Environment +.vscode/ +.direnv + +# Rust Cargo.lock */target /target -.vscode/ +# Trunk +dist diff --git a/Cargo.toml b/Cargo.toml index 1913db3..9f47fbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,21 @@ [workspace] +resolver = "2" members = [ - "opensi", - "opensi-client", - "opensi-editor" + "crates/opensi-core", + "crates/opensi-editor" ] + +[workspace.package] +authors = ["barsoosayque ", "snpefk "] +edition = "2021" +license = "MIT" +rust-version = "1.76" +version = "0.1.0" + +[profile.release] +opt-level = 2 # fast and small wasm + +# Optimize all dependencies even in debug builds: +[profile.dev.package."*"] +opt-level = 2 + diff --git a/LICENSE b/LICENSE index aca39cc..16e4229 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 barsoosayque +Copyright (c) 2024 opensi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 6cc5888..78b19db 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,21 @@ -# opensi +# opensi-editor ![Rust](https://github.com/opensi/opensi/workflows/Rust/badge.svg) -Open SI Game - реализация популярной телеигры на Rust +Редактор вопросов для популярной реализации "Своей игры": [SiGame](https://vladimirkhil.com/si/game) -## Пакеты с игрой - -opensi совместимыми пакетим формата `*.siq` из популярной реализации "Своей игры" - [SiGame](https://vladimirkhil.com/si/game) - -## Разработка - -Редактор зависит от GTK, поэтому перед разработкой стоит [установить](http://gtk-rs.org/docs/requirements.html) **GTK+**, **GLib** и **Cairo** - -### Debian & Ubuntu - -```shell -sudo apt-get install libgtk-3-dev -``` - -### Fedora - -```shell -$ sudo dnf install gtk3-devel glib2-devel +Доступен в веб формате: [OpenSI Editor Web](opensi.github.io/opensi-editor). -### Fedora 21 and earlier -$ sudo yum install gtk3-devel glib2-devel +## Пакеты с игрой -``` +OpenSI Editor совместим с пакетами формата `*.siq` из SIGame. ## Сборка и запуск -Для клиента игры и для редактора существуют отдельные bin-конфигурации: - ```shell -# Клиент -$ cargo run --bin opensi-client +# Обычный запуск нативной версии +$ cargo run opensi-editor -# Редактор -$ cargo run --bin opensi-editor +# Запуск веб-версии (потребуется установка trunk) +$ trunk serve crates/opensi-editor/index.html ``` diff --git a/crates/opensi-core/Cargo.toml b/crates/opensi-core/Cargo.toml new file mode 100644 index 0000000..0e434be --- /dev/null +++ b/crates/opensi-core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "opensi-core" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[dependencies] +serde = { version = "1.0", features = [ "derive" ] } +quick-xml = { version = "0.17", features = [ "serialize" ] } +zip = { version = "0.5.4", default-features = false, features = ["deflate"] } +percent-encoding = "2.1.0" diff --git a/opensi/src/lib.rs b/crates/opensi-core/src/lib.rs similarity index 65% rename from opensi/src/lib.rs rename to crates/opensi-core/src/lib.rs index 692cf67..042c772 100644 --- a/opensi/src/lib.rs +++ b/crates/opensi-core/src/lib.rs @@ -1,13 +1,11 @@ -extern crate quick_xml; -extern crate serde; +#![allow(dead_code)] -use std::fs::File; -use std::io::prelude::*; use std::io::ErrorKind; use std::path::Path; +use std::{fs::File, io::Read}; use PartialEq; -use quick_xml::de::{from_reader, from_str}; +use quick_xml::de::from_str; use serde::Deserialize; #[derive(Clone, Debug, Deserialize, PartialEq)] @@ -155,7 +153,7 @@ impl Atom { } impl Package { - pub fn open>(path: P) -> Result { + pub fn open(path: impl AsRef) -> Result { let package_file = File::open(path)?; let mut zip = zip::ZipArchive::new(package_file)?; let mut xml = zip.by_name("content.xml")?; @@ -165,56 +163,52 @@ impl Package { from_str(&contents).map_err(|e| std::io::Error::new(ErrorKind::InvalidData, e)) } - pub fn open_with_extraction>(path: P) -> Result { - let package_name = path.as_ref().file_name().unwrap().to_str().unwrap(); - let package_file = File::open(&path)?; - let mut zip = zip::ZipArchive::new(package_file)?; - - let xml_path = Self::extract_package(package_name, &mut zip)?; - let xml = std::fs::File::open(xml_path)?; - let reader = std::io::BufReader::new(xml); - from_reader(reader).map_err(|e| std::io::Error::new(ErrorKind::InvalidData, e)) - } - - /// Extract package internals into `/tmp/{package_name}/`. Return `PathBuf` - /// to `content.xml`. - /// - /// # Arguments - /// * `package_name` - package name to which the package will be extracted - /// * `zip` - unpacked archive - fn extract_package( - package_name: &str, - zip: &mut zip::ZipArchive, - ) -> Result { - let tmp = std::env::temp_dir().join(package_name); - - for i in 0..zip.len() { - let mut zipfile = zip.by_index(i)?; - let mut zipfile_path = zipfile.sanitized_name(); - let encoded_name = zipfile_path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string(); - - if encoded_name.starts_with("@") { - zipfile_path.set_file_name(&encoded_name[1..]); - } else { - zipfile_path.set_file_name(encoded_name) - }; - - if let Some(parent) = zipfile_path.parent() { - let parent_path = tmp.join(parent); - if !parent.exists() { - std::fs::create_dir_all(&parent_path)?; - } - } - - let path = &tmp.join(zipfile_path); - let mut fsfile = std::fs::File::create(&path)?; - std::io::copy(&mut zipfile, &mut fsfile)?; - } - Ok(tmp.join("content.xml")) - } + // TODO: figure out how to do extraction on wasm + // pub async fn open_with_extraction(path: impl AsRef) -> Result { + // let package_name = path.as_ref().file_name().unwrap().to_str().unwrap(); + // let package_file = File::open(&path)?; + // let mut zip = zip::ZipArchive::new(package_file)?; + + // let xml_path = Self::extract_package(package_name, &mut zip)?; + // let xml = std::fs::File::open(xml_path)?; + // let reader = std::io::BufReader::new(xml); + // from_reader(reader).map_err(|e| std::io::Error::new(ErrorKind::InvalidData, e)) + // } + + // /// Extract package internals into `/tmp/{package_name}/`. Return `PathBuf` + // /// to `content.xml`. + // /// + // /// # Arguments + // /// * `package_name` - package name to which the package will be extracted + // /// * `zip` - unpacked archive + // async fn extract_package( + // package_name: &str, + // zip: &mut zip::ZipArchive, + // ) -> Result { + // let tmp = std::env::temp_dir().join(package_name); + + // for i in 0..zip.len() { + // let mut zipfile = zip.by_index(i)?; + // let mut zipfile_path = zipfile.sanitized_name(); + // let encoded_name = zipfile_path.file_name().unwrap().to_str().unwrap().to_string(); + + // if encoded_name.starts_with("@") { + // zipfile_path.set_file_name(&encoded_name[1..]); + // } else { + // zipfile_path.set_file_name(encoded_name) + // }; + + // if let Some(parent) = zipfile_path.parent() { + // let parent_path = tmp.join(parent); + // if !parent.exists() { + // std::fs::create_dir_all(&parent_path)?; + // } + // } + + // let path = &tmp.join(zipfile_path); + // let mut fsfile = std::fs::File::create(&path)?; + // std::io::copy(&mut zipfile, &mut fsfile)?; + // } + // Ok(tmp.join("content.xml")) + // } } diff --git a/tests/data/slamjam2.siq b/crates/opensi-core/tests/data/slamjam2.siq similarity index 100% rename from tests/data/slamjam2.siq rename to crates/opensi-core/tests/data/slamjam2.siq diff --git a/tests/test.rs b/crates/opensi-core/tests/test.rs similarity index 83% rename from tests/test.rs rename to crates/opensi-core/tests/test.rs index f2d18cb..516f0b5 100644 --- a/tests/test.rs +++ b/crates/opensi-core/tests/test.rs @@ -1,7 +1,7 @@ -#[path = "../src/core/lib.rs"] -mod opensi; +#[path = "../src/lib.rs"] +mod opensi_core; -use opensi::Package; +use opensi_core::Package; const PATH: &str = "tests/data/slamjam2.siq"; @@ -13,7 +13,7 @@ fn open_pack() { #[test] fn read_package_name() { - let package = Package::open(PATH).expect("pack is not found"); + let package = Package::open(PATH).expect("pack is not found"); assert_eq!(package.name.is_some(), true); assert_eq!(package.name.unwrap(), "SLAM JAM 2".to_owned()); } diff --git a/crates/opensi-editor/Cargo.toml b/crates/opensi-editor/Cargo.toml new file mode 100644 index 0000000..3ff239c --- /dev/null +++ b/crates/opensi-editor/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "opensi-editor" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[dependencies] +opensi-core = { version = "*", path = "../opensi-core" } + +egui = "0.28" +eframe = { version = "0.28", default-features = false, features = [ + "default_fonts", # Embed the default egui fonts. + "glow", # Use the glow rendering backend. Alternative: "wgpu". + "persistence", # Enable restoring app state when restarting the app. +] } +log = "0.4" +rfd = "0.14.1" +# https://github.com/emilk/egui/issues/4961 +web-sys = "=0.3.69" + +# You only need serde if you want app persistence: +serde = { version = "1", features = ["derive"] } + +# native: +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +env_logger = "0.10" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } + +# web: +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { version = "1", features = ["macros", "rt"] } +tokio_with_wasm = { version = "0.6.1", features = ["macros", "rt"] } + +# to access the DOM (to hide the loading text) +[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] +version = "0.3.4" diff --git a/crates/opensi-editor/Trunk.toml b/crates/opensi-editor/Trunk.toml new file mode 100644 index 0000000..1954408 --- /dev/null +++ b/crates/opensi-editor/Trunk.toml @@ -0,0 +1,3 @@ +[build] +release = true +offline = true diff --git a/crates/opensi-editor/assets/favicon.ico b/crates/opensi-editor/assets/favicon.ico new file mode 100644 index 0000000..248af59 Binary files /dev/null and b/crates/opensi-editor/assets/favicon.ico differ diff --git a/crates/opensi-editor/assets/icon-1024.png b/crates/opensi-editor/assets/icon-1024.png new file mode 100644 index 0000000..625764f Binary files /dev/null and b/crates/opensi-editor/assets/icon-1024.png differ diff --git a/crates/opensi-editor/assets/icon-256.png b/crates/opensi-editor/assets/icon-256.png new file mode 100644 index 0000000..b4c7094 Binary files /dev/null and b/crates/opensi-editor/assets/icon-256.png differ diff --git a/crates/opensi-editor/assets/icon_ios_touch_192.png b/crates/opensi-editor/assets/icon_ios_touch_192.png new file mode 100644 index 0000000..351a5af Binary files /dev/null and b/crates/opensi-editor/assets/icon_ios_touch_192.png differ diff --git a/crates/opensi-editor/assets/manifest.json b/crates/opensi-editor/assets/manifest.json new file mode 100644 index 0000000..ee1bf9d --- /dev/null +++ b/crates/opensi-editor/assets/manifest.json @@ -0,0 +1,28 @@ +{ + "name": "opensi-editor", + "short_name": "opensi-editor", + "icons": [ + { + "src": "./icon-256.png", + "sizes": "256x256", + "type": "image/png" + }, + { + "src": "./maskable_icon_x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "./icon-1024.png", + "sizes": "1024x1024", + "type": "image/png" + } + ], + "lang": "en-US", + "id": "/index.html", + "start_url": "./index.html", + "display": "standalone", + "background_color": "white", + "theme_color": "white" +} diff --git a/crates/opensi-editor/assets/maskable_icon_x512.png b/crates/opensi-editor/assets/maskable_icon_x512.png new file mode 100644 index 0000000..e09e7cf Binary files /dev/null and b/crates/opensi-editor/assets/maskable_icon_x512.png differ diff --git a/crates/opensi-editor/assets/sw.js b/crates/opensi-editor/assets/sw.js new file mode 100644 index 0000000..cd28cbb --- /dev/null +++ b/crates/opensi-editor/assets/sw.js @@ -0,0 +1,25 @@ +var cacheName = 'opensi-editor-pwa'; +var filesToCache = [ + './', + './index.html', + './opensi_editor.js', + './opensi_editor_bg.wasm', +]; + +/* Start the service worker and cache all of the app's content */ +self.addEventListener('install', function (e) { + e.waitUntil( + caches.open(cacheName).then(function (cache) { + return cache.addAll(filesToCache); + }) + ); +}); + +/* Serve cached content when offline */ +self.addEventListener('fetch', function (e) { + e.respondWith( + caches.match(e.request).then(function (response) { + return response || fetch(e.request); + }) + ); +}); diff --git a/crates/opensi-editor/assets/ui/Lora-Medium.ttf b/crates/opensi-editor/assets/ui/Lora-Medium.ttf new file mode 100644 index 0000000..85ca5a2 Binary files /dev/null and b/crates/opensi-editor/assets/ui/Lora-Medium.ttf differ diff --git a/crates/opensi-editor/assets/ui/Lora-MediumItalic.ttf b/crates/opensi-editor/assets/ui/Lora-MediumItalic.ttf new file mode 100644 index 0000000..42208fb Binary files /dev/null and b/crates/opensi-editor/assets/ui/Lora-MediumItalic.ttf differ diff --git a/crates/opensi-editor/assets/ui/OFL.txt b/crates/opensi-editor/assets/ui/OFL.txt new file mode 100644 index 0000000..3f0fcd6 --- /dev/null +++ b/crates/opensi-editor/assets/ui/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Lora Project Authors (https://github.com/cyrealtype/Lora-Cyrillic), with Reserved Font Name "Lora". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/crates/opensi-editor/index.html b/crates/opensi-editor/index.html new file mode 100644 index 0000000..d218a40 --- /dev/null +++ b/crates/opensi-editor/index.html @@ -0,0 +1,147 @@ + + + + + + + + + OpenSI Editor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Loading… +

+
+
+ + + + + + + + + diff --git a/crates/opensi-editor/src/app.rs b/crates/opensi-editor/src/app.rs new file mode 100644 index 0000000..714dcff --- /dev/null +++ b/crates/opensi-editor/src/app.rs @@ -0,0 +1,109 @@ +use rfd::AsyncFileDialog; + +#[cfg(not(target_arch = "wasm32"))] +use tokio; +#[cfg(target_arch = "wasm32")] +use tokio_with_wasm::alias as tokio; + +const FONT_REGULAR_ID: &'static str = "Regular"; +const FONT_ITALIC_ID: &'static str = "Italic"; + +/// Main context for the whole app. +/// Serialized fields are saved and restored. +#[derive(serde::Deserialize, serde::Serialize, Default, Debug)] +#[serde(default)] +pub struct EditorApp {} + +impl EditorApp { + /// Called once before the first frame. + pub fn new(cc: &eframe::CreationContext<'_>) -> Self { + // Load previous app state (if any). + // Note that you must enable the `persistence` feature for this to work. + let app: Self = if let Some(storage) = cc.storage { + eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default() + } else { + Default::default() + }; + + let mut font_definitions = egui::FontDefinitions::default(); + font_definitions.font_data.insert( + FONT_REGULAR_ID.into(), + egui::FontData::from_static(include_bytes!("../assets/ui/Lora-Medium.ttf")), + ); + font_definitions.font_data.insert( + FONT_ITALIC_ID.into(), + egui::FontData::from_static(include_bytes!("../assets/ui/Lora-MediumItalic.ttf")), + ); + if let Some(family) = font_definitions.families.get_mut(&egui::FontFamily::Proportional) { + family.insert(0, FONT_REGULAR_ID.into()); + } + cc.egui_ctx.set_fonts(font_definitions); + + app + } +} + +impl eframe::App for EditorApp { + /// Called by the frame work to save state before shutdown. + fn save(&mut self, storage: &mut dyn eframe::Storage) { + eframe::set_value(storage, eframe::APP_KEY, self); + } + + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + egui::menu::bar(ui, |ui| { + egui::widgets::global_dark_light_mode_switch(ui); + ui.add_space(16.0); + ui.menu_button("Файл", |ui| { + if ui.button("⮩ Импорт").clicked() { + let _result = tokio::spawn(async { + let file = AsyncFileDialog::new() + .set_title("Выбрать файл с вопросами для импорта") + .add_filter("SIGame Pack", &["siq"]) + .set_directory("/") + .set_can_create_directories(false) + .pick_file() + .await?; + + Some(file.read().await) + }); + // TODO + } + if ui.button("💾 Сохранить").clicked() { + let _result = tokio::spawn(async { + let file = AsyncFileDialog::new() + .set_title("Сохранить выбранный пакет с вопросами") + .set_directory("/") + .set_file_name("pack.siq") + .save_file() + .await?; + + let data = [0]; + file.write(&data).await.ok() + }); + // TODO + } + + if !cfg!(target_arch = "wasm32") { + ui.separator(); + if ui.button("Выйти").clicked() { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } + } + }); + }); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + ui.with_layout( + egui::Layout::centered_and_justified(egui::Direction::LeftToRight), + |ui| { + let text = egui::RichText::new("We're so back") + .size(100.0) + .color(ui.style().visuals.weak_text_color()); + ui.add(egui::Label::new(text)); + }, + ); + }); + } +} diff --git a/crates/opensi-editor/src/lib.rs b/crates/opensi-editor/src/lib.rs new file mode 100644 index 0000000..9b08806 --- /dev/null +++ b/crates/opensi-editor/src/lib.rs @@ -0,0 +1,4 @@ +#![warn(clippy::all)] + +mod app; +pub use app::EditorApp; diff --git a/crates/opensi-editor/src/main.rs b/crates/opensi-editor/src/main.rs new file mode 100644 index 0000000..4f1cc30 --- /dev/null +++ b/crates/opensi-editor/src/main.rs @@ -0,0 +1,61 @@ +#![warn(clippy::all)] +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +// When compiling natively: +#[cfg(not(target_arch = "wasm32"))] +#[tokio::main] +async fn main() -> eframe::Result { + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + + let native_options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([400.0, 300.0]) + .with_min_inner_size([300.0, 220.0]) + .with_icon( + eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..]) + .expect("Failed to load icon"), + ), + ..Default::default() + }; + eframe::run_native( + "OpenSI Editor", + native_options, + Box::new(|cc| Ok(Box::new(opensi_editor::EditorApp::new(cc)))), + ) +} + +// When compiling to web using trunk: +#[cfg(target_arch = "wasm32")] +#[tokio::main(flavor = "current_thread")] +async fn main() { + // Redirect `log` message to `console.log` and friends: + eframe::WebLogger::init(log::LevelFilter::Debug).ok(); + + let web_options = eframe::WebOptions::default(); + + let start_result = eframe::WebRunner::new() + .start( + "the_canvas_id", + web_options, + Box::new(|cc| Ok(Box::new(opensi_editor::EditorApp::new(cc)))), + ) + .await; + + // Remove the loading text and spinner: + let loading_text = web_sys::window() + .and_then(|w| w.document()) + .and_then(|d| d.get_element_by_id("loading_text")); + if let Some(loading_text) = loading_text { + match start_result { + Ok(_) => { + loading_text.remove(); + }, + Err(e) => { + loading_text.set_inner_html( + "

The app has crashed. See the developer console for details.

", + ); + panic!("Failed to start eframe: {e:?}"); + }, + } + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..2719bc5 --- /dev/null +++ b/flake.lock @@ -0,0 +1,103 @@ +{ + "nodes": { + "crane": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1724006180, + "narHash": "sha256-PVxPj0Ga2fMYMtcT9ARCthF+4U71YkOT7ZjgD/vf1Aw=", + "owner": "ipetkov", + "repo": "crane", + "rev": "7ce92819802bc583b7e82ebc08013a530f22209f", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1724047581, + "narHash": "sha256-BypLrnMS2QvvdVhwWixppTOM3fLPC8eyJse0BNSbbfI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9b5094b8f6e06a46f9f53bb97a9573b7cedf2a2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", + "utils": "utils" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1724034091, + "narHash": "sha256-b1g7w0sw+MDAhUAeCoX1vlTghsqcDZkxr+k9OZmxPa8=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "c7d36e0947826e0751a5214ffe82533fbc909bc0", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..9f8736f --- /dev/null +++ b/flake.nix @@ -0,0 +1,94 @@ +{ + inputs = { + nixpkgs = { + url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + utils = { + url = "github:numtide/flake-utils"; + }; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + crane = { + url = "github:ipetkov/crane"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, utils, rust-overlay, crane }: + utils.lib.eachSystem ["aarch64-linux" "x86_64-linux"] (system: + let + overlays = [(import rust-overlay)]; + pkgs = import nixpkgs { inherit system overlays; }; + craneLib = crane.mkLib pkgs; + + # Build inputs + requiredPrograms = with pkgs; [ + (rust-bin.fromRustupToolchainFile ./rust-toolchain.toml) + trunk + pkg-config + ]; + + # Compiletime/Runtime deps (native linux build) + requiredLibsLinux = with pkgs; [ + # misc. libraries + openssl + + # GUI libs + libxkbcommon + libGL + fontconfig + + # wayland libraries + wayland + + # x11 libraries + xorg.libXcursor + xorg.libXrandr + xorg.libXi + xorg.libX11 + ]; + + # IDE/shell dependencies + developPrograms = with pkgs; [ + clippy + rust-analyzer-unwrapped + cargo-edit + ]; + + # Autofetch project info from Cargo + cargoDesc = pkgs.lib.trivial.importTOML ./Cargo.toml; + projectName = cargoDesc.package.name; + projectVersion = cargoDesc.package.version; + + packageDef = rec { + pname = projectName; + version = projectVersion; + + src = + pkgs.lib.cleanSourceWith { + src = ./.; + filter = path: type: craneLib.filterCargoSources path type; + }; + + # https://github.com/NixOS/nix/issues/4623 + # GIT_LFS_SKIP_SMUDGE = 1; + strictDeps = true; + nativeBuildInputs = requiredPrograms; + }; + in + rec { + # `nix develop` + devShells.default = pkgs.mkShell rec { + nativeBuildInputs = developPrograms ++ requiredPrograms; + buildInputs = requiredLibsLinux; + + shellHook = '' + RUST_LIBDIR=$(rustc --print target-libdir) + FLAKE_LIBDIR="${pkgs.lib.makeLibraryPath buildInputs}" + export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$FLAKE_LIBDIR:$RUST_LIBDIR:target/debug/deps" + ''; + }; + }); +} diff --git a/opensi-client/Cargo.toml b/opensi-client/Cargo.toml deleted file mode 100644 index 5dfd12e..0000000 --- a/opensi-client/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "opensi-client" -version = "0.1.0" -authors = ["barsoosayque "] -edition = "2018" - -[dependencies] -opensi = { version = "*", path = "../opensi" } - -druid = "0.6.0" diff --git a/opensi-client/src/main.rs b/opensi-client/src/main.rs deleted file mode 100644 index b76857f..0000000 --- a/opensi-client/src/main.rs +++ /dev/null @@ -1,12 +0,0 @@ -use druid::{AppLauncher, WindowDesc, Widget, PlatformError}; -use druid::widget::Label; - -fn build_ui() -> impl Widget<()> { - Label::new("Hello world") -} - -fn main() -> Result<(), PlatformError> { - AppLauncher::with_window(WindowDesc::new(build_ui)).launch(())?; - Ok(()) -} - diff --git a/opensi-editor/Cargo.toml b/opensi-editor/Cargo.toml deleted file mode 100644 index 50b060c..0000000 --- a/opensi-editor/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "opensi-editor" -version = "0.1.0" -authors = ["snpefk "] -edition = "2018" - -[dependencies] -opensi = { version = "*", path = "../opensi" } - -gtk = "^0.8.0" -relm = "^0.19.0" -relm-derive = "^0.19.0" -gdk = "^0.12.0" -gdk-pixbuf = "^0.8.0" -zip = "0.5.4" diff --git a/opensi-editor/src/editor.ui b/opensi-editor/src/editor.ui deleted file mode 100644 index 7b8e7f5..0000000 --- a/opensi-editor/src/editor.ui +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - False - 440 - 550 - True - False - - - True - False - 6 - - - True - True - never - in - - - True - True - - - - - - True - Дерево пакета - - - - 0 - - - - - - - - - False - True - 0 - - - - - True - False - vertical - - - False - 8 - - - True - False - вопрос: - - - False - True - 10 - 0 - - - - - True - True - True - - - False - True - 1 - - - - - False - True - 0 - - - - - True - gtk-missing-image - - - True - True - 1 - - - - - True - False - - - False - True - 2 - - - - - False - 8 - 8 - - - True - False - ответ: - 7 - - - False - True - 10 - 0 - - - - - True - True - True - - - False - True - 1 - - - - - False - True - 4 - - - - - True - gtk-missing-image - - - True - True - 3 - - - - - False - True - 8 - 1 - - - - - - - True - False - Editor - True - - - True - True - True - - - - - - diff --git a/opensi-editor/src/main.rs b/opensi-editor/src/main.rs deleted file mode 100644 index fa43d26..0000000 --- a/opensi-editor/src/main.rs +++ /dev/null @@ -1,327 +0,0 @@ -use gtk::prelude::*; -use relm::{connect, Relm, Update, Widget}; -use relm_derive::Msg; - -#[derive(Msg)] -enum Msg { - PackageSelect, - ItemSelect, - Quit, -} - -struct Win { - window: gtk::Window, - file_chooser: gtk::FileChooserButton, - tree_view: gtk::TreeView, - body_container: gtk::Box, - body_editor: gtk::Entry, - body_label: gtk::Label, - image_preview: gtk::Image, - editor_container: gtk::Box, - answer_entry: gtk::Entry, - answer_image: gtk::Image, - answer_container: gtk::Box, - model: Model, -} - -struct Model { - store: Option, - filename: Option, -} - -struct GtkPackageStore { - store: gtk::TreeStore, - chunks: Vec, -} - -impl GtkPackageStore { - fn new(package: opensi::Package) -> GtkPackageStore { - let store = gtk::TreeStore::new(&[String::static_type(), u32::static_type()]); - let columns = &[0u32, 1u32]; - - let mut chunks = Vec::new(); - let mut i = 0u32; - - package.rounds.rounds.iter().for_each(|round| { - let round_parent = store.insert_with_values(None, None, columns, &[&round.name, &i]); - i += 1; - chunks.push(Chunk::Round(round.clone())); - - round.themes.themes.iter().for_each(|theme| { - let theme_parent = store.insert_with_values( - Some(&round_parent), - None, - columns, - &[&theme.name, &i], - ); - - i += 1; - chunks.push(Chunk::Theme(theme.clone())); - - theme.questions.questions.iter().for_each(|question| { - store.insert_with_values( - Some(&theme_parent), - None, - columns, - &[&question.price.to_string(), &i], - ); - - i += 1; - chunks.push(Chunk::Question(question.clone())); - }) - }); - }); - GtkPackageStore { store, chunks } - } - - fn get_chunk(&self, model: >k::TreeModel, iter: >k::TreeIter) -> Chunk { - let index = model - .get_value(&iter, 1) - .get::() - .ok() - .and_then(|value| value) - .expect("get_value.get failed"); - - let chunk = &self.chunks[index as usize]; - chunk.clone() // :^) - } -} - -impl Update for Win { - type Model = Model; - type ModelParam = (); - type Msg = Msg; - - fn model(_: &Relm, _: ()) -> Model { - Model { - store: None, - filename: None, - } - } - - fn update(&mut self, event: Msg) { - match event { - Msg::PackageSelect => { - let filename = self.file_chooser.get_filename().unwrap(); - let gtkstore = opensi::Package::open_with_extraction(&filename) - .map(GtkPackageStore::new) - .expect("Failed to create store"); - - self.model.filename = Some(filename); - self.tree_view - .set_model::(Some(>kstore.store.as_ref())); - self.model.store = Some(gtkstore); - } - - Msg::ItemSelect => { - self.image_preview.set_visible(false); - self.body_container.set_visible(false); - self.answer_container.set_visible(false); - self.answer_image.set_visible(false); - - let selection = self.tree_view.get_selection(); - if let Some((model, iter)) = selection.get_selected() { - let store = self.model.store.as_ref().unwrap(); - let chunk = store.get_chunk(&model, &iter); - - match chunk { - Chunk::Round(round) => { - draw_round(self, &round); - // dbg!(round); - } - Chunk::Theme(theme) => { - draw_theme(self, &theme); - // dbg!(theme); - } - Chunk::Question(question) => { - draw_question(self, &question); - // dbg!(question); - } - } - } - } - - Msg::Quit => gtk::main_quit(), - } - } -} - -fn draw_image(image_widget: >k::Image, path: std::path::PathBuf) { - let parent = image_widget.get_parent().unwrap(); - let allocation = parent.get_allocation(); - let mut pixbuf = gdk_pixbuf::Pixbuf::new_from_file(path).unwrap(); - - if pixbuf.get_width() > allocation.width || pixbuf.get_height() > allocation.height { - pixbuf = scale_image(allocation, pixbuf).unwrap(); - } - - image_widget.set_from_pixbuf(Some(pixbuf.as_ref())); - image_widget.set_visible(true); -} - -fn scale_image( - allocation: gtk::Rectangle, - image: gdk_pixbuf::Pixbuf, -) -> Option { - let ratio_width = allocation.width as f32 / image.get_width() as f32; - let ratio_height = allocation.height as f32 / image.get_height() as f32; - let ratio = ratio_width.min(ratio_height); - - let new_width = (image.get_width() as f32 * ratio).floor() as i32; - let new_height = (image.get_height() as f32 * ratio).floor() as i32; - - image.scale_simple(new_width, new_height, gdk_pixbuf::InterpType::Bilinear) -} - -fn draw_round(win: &Win, round: &opensi::Round) { - win.body_container.set_visible(true); - win.body_editor.set_text(&round.name); - win.body_label.set_text("раунд:"); -} - -fn draw_theme(win: &Win, theme: &opensi::Theme) { - win.body_container.set_visible(true); - win.body_editor.set_text(&theme.name); - win.body_label.set_text("тема:"); -} - -fn draw_question(win: &Win, question: &opensi::Question) { - let body = question - .scenario - .atoms - .first() - .unwrap() - .body - .as_ref() - .unwrap(); - win.body_editor.set_text(body); - - let mut atoms_slices = question.scenario.atoms.split(|atom| { - atom.variant - .as_ref() - .unwrap_or(&String::from("heh")) - .eq("marker") - }); - - atoms_slices.next().unwrap().iter().for_each(|atom| { - // empty variant means text atom - if atom.variant.is_none() { - let body = atom.body.as_ref().unwrap(); - win.body_container.set_visible(true); - win.body_label.set_text("вопрос:"); - win.body_editor.set_text(body); - return; - } - - let path = win - .model - .filename - .as_ref() - .and_then(|x| x.file_name()) - .and_then(|x| x.to_str()) - .unwrap(); - - if let Some(resource) = atom.get_resource(path) { - match resource { - opensi::Resource::Image(path) => draw_image(&win.image_preview, path), - _ => {} - } - } - }); - - if let Some(atoms) = atoms_slices.next() { - if atoms.len() > 0 { - let atom = atoms.last().unwrap(); - if atom.variant.is_some() { - let path = win - .model - .filename - .as_ref() - .and_then(|x| x.file_name()) - .and_then(|x| x.to_str()) - .unwrap(); - if let Some(resource) = atom.get_resource(path) { - match resource { - opensi::Resource::Image(path) => { - draw_image(&win.answer_image, path); - } - _ => {} - } - } - } - } - } - - question.right.answers.iter().for_each(|answer| { - win.answer_container.set_visible(true); - if let Some(body) = answer.body.as_ref() { - win.answer_entry.set_text(body); - } else { - win.answer_entry.set_text(""); - } - }) -} - -impl Widget for Win { - type Root = gtk::Window; - - fn root(&self) -> Self::Root { - self.window.clone() - } - - fn view(relm: &Relm, model: Self::Model) -> Self { - let source = include_str!("editor.ui"); - let builder = gtk::Builder::new_from_string(source); - let window: gtk::Window = builder.get_object("editor").unwrap(); - - let tree_view: gtk::TreeView = builder.get_object("tree").unwrap(); - let file_chooser: gtk::FileChooserButton = builder.get_object("file-chooser").unwrap(); - let body_editor: gtk::Entry = builder.get_object("body-editor").unwrap(); - let image_preview: gtk::Image = builder.get_object("image-preview-editor").unwrap(); - let body_container: gtk::Box = builder.get_object("body-container").unwrap(); - let body_label: gtk::Label = builder.get_object("body-label").unwrap(); - - let answer_entry: gtk::Entry = builder.get_object("answer-entry").unwrap(); - let answer_container: gtk::Box = builder.get_object("answer-container").unwrap(); - let answer_image: gtk::Image = builder.get_object("image-answer").unwrap(); - - let editor_container: gtk::Box = builder.get_object("editor-container").unwrap(); - - window.show(); - - connect!(relm, file_chooser, connect_file_set(_), Msg::PackageSelect); - connect!(relm, tree_view, connect_cursor_changed(_), Msg::ItemSelect); - connect!( - relm, - window, - connect_delete_event(_, _), - return (Some(Msg::Quit), Inhibit(false)) - ); - - Win { - window, - file_chooser, - tree_view, - body_editor, - image_preview, - body_container, - body_label, - editor_container, - answer_entry, - answer_image, - answer_container, - model, - } - } -} - -#[derive(Debug, Clone)] -pub enum Chunk { - Round(opensi::Round), - Theme(opensi::Theme), - Question(opensi::Question), -} - -fn main() { - Win::run(()).expect("Window failed to run"); -} diff --git a/opensi/Cargo.toml b/opensi/Cargo.toml deleted file mode 100644 index bf8a87b..0000000 --- a/opensi/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "opensi" -version = "0.1.0" -authors = ["barsoosayque ", "snpefk "] -edition = "2018" - -[dependencies] -serde = { version = "1.0", features = [ "derive" ] } -quick-xml = { version = "0.17", features = [ "serialize" ] } -zip = "0.5.4" -percent-encoding = "2.1.0" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..9e7529b --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "stable" +components = [ "rust-src", "rustfmt", "rustc-dev" ] +targets = [ "wasm32-unknown-unknown" ] +profile = "default" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..481ad0c --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,24 @@ +edition = "2021" +unstable_features = true +version = "Two" + +format_macro_matchers = true +format_macro_bodies = true +format_code_in_doc_comments = true + +max_width = 100 +fn_single_line = true +empty_item_single_line = true +struct_lit_single_line = true +match_block_trailing_comma = true +overflow_delimited_expr = true +use_small_heuristics = "Max" +condense_wildcard_suffixes = true +use_field_init_shorthand = true + +reorder_modules = true +reorder_impl_items = true +reorder_imports = true + +imports_granularity = "Crate" +group_imports = "StdExternalCrate"