chore(tauri) bump versions to 0.5.0 (#532) version updates (#533)

* chore(tauri) bump versions to 0.5.0

* fix(tauri) correct dependency versions
version updates

Co-authored-by: lucasfernog <lucas@tauri.studio>
This commit is contained in:
github-actions[bot] 2020-03-28 16:52:12 +01:00 committed by GitHub
parent ae1e960d59
commit c5ee5f8c6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
128 changed files with 5516 additions and 1871 deletions

View File

@ -65,6 +65,12 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: install webkit2gtk (ubuntu only)
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.0
- run: cargo install --path ./cli/tauri-bundler --force
- name: test
timeout-minutes: 15
run: |

View File

@ -8,8 +8,8 @@ IF "%cd%\"=="%~dp0" (
)
rem setup relative paths from root folder
set "TAURI_DIST_DIR=%~1tauri\test\fixture\dist"
set "TAURI_DIR=%~1tauri\test\fixture\src-tauri"
set "TAURI_DIST_DIR=%~1tauri\examples\communication\dist"
set "TAURI_DIR=%~1tauri\examples\communication\src-tauri"
rem convert relative path to absolute path and re-set it into the enviroment var
for /F "delims=" %%F IN ("%TAURI_DIST_DIR%") DO SET "TAURI_DIST_DIR=%%~fF"
for /F "delims=" %%F IN ("%TAURI_DIR%") DO SET "TAURI_DIR=%%~fF"

View File

@ -1,7 +1,7 @@
Write-Output "Setting up enviromental Variables"
# setup relative paths
$dist_path = "tauri\test\fixture\dist"
$src_path = "tauri\test\fixture\src-tauri"
$dist_path = "tauri\examples\communication\dist"
$src_path = "tauri\examples\communication\src-tauri"
# check to see if path variables are directories
if ((Test-Path $dist_path -PathType Any) -Or (Test-Path $src_path -PathType Any)) {

View File

@ -2,8 +2,8 @@
# Note: Script must be run like this `. .init_env.sh` to setup variables for your current shell
# define relative paths
DistPath='tauri/test/fixture/dist'
SrcPath='tauri/test/fixture/src-tauri'
DistPath='tauri/examples/communication/dist'
SrcPath='tauri/examples/communication/src-tauri'
echo "Setting up enviroment Variables"

View File

@ -20,6 +20,9 @@ Tauri is a tool for building tiny, blazing fast binaries for all major desktop p
| tauri.js CLI | [![](https://img.shields.io/npm/v/tauri.svg)](https://www.npmjs.com/package/tauri) |✅|✅|✅|
| tauri core | [![](https://img.shields.io/crates/v/tauri.svg)](https://crates.io/crates/tauri) |✅|✅|✅|
| tauri bundler | [![](https://img.shields.io/crates/v/tauri-bundler.svg)](https://crates.io/crates/tauri-bundler) |✅|✅|✅ |
| tauri api | [![](https://img.shields.io/crates/v/tauri-api.svg)](https://crates.io/crates/tauri-api) |✅|✅|✅ |
| tauri updater | [![](https://img.shields.io/crates/v/tauri-updater.svg)](https://crates.io/crates/tauri-updater) |✅|✅|✅ |
| tauri utils | [![](https://img.shields.io/crates/v/tauri-utils.svg)](https://crates.io/crates/tauri-utils) |✅|✅|✅ |
## Who Tauri is For
Because of the way Tauri has been built and can be extended, developers

View File

@ -2,7 +2,7 @@ workspace = {}
[package]
name = "tauri-bundler"
version = "0.4.4"
version = "0.5.0"
authors = ["George Burton <burtonageo@gmail.com>", "Lucas Fernandes Gonçalves Nogueira <lucas@quasar.dev>", "Daniel Thompson-Yvetot <denjell@sfosc.org>", "Tensor Programming <tensordeveloper@gmail.com>"]
license = "MIT/Apache-2.0"
keywords = ["bundle", "cargo", "tauri"]
@ -18,7 +18,7 @@ dirs = "2.0.2"
error-chain = "0.12"
glob = "0.3.0"
icns = "0.3"
image = "0.22.5"
image = "0.23.2"
libflate = "0.1"
md5 = "0.7.0"
msi = "0.2"
@ -34,7 +34,7 @@ uuid = { version = "0.8", features = ["v5"] }
walkdir = "2"
[target.'cfg(target_os = "windows")'.dependencies]
attohttpc = { version = "0.11.1" }
attohttpc = { version = "0.12.0" }
regex = { version = "1" }
[target.'cfg(not(target_os = "linux"))'.dependencies]

View File

@ -23,7 +23,7 @@ use crate::{ResultExt, Settings};
use ar;
use icns;
use image::png::{PNGDecoder, PNGEncoder};
use image::png::{PngDecoder};
use image::{self, GenericImageView, ImageDecoder};
use libflate::gzip;
use md5;
@ -32,7 +32,6 @@ use tar;
use walkdir::WalkDir;
use std::collections::BTreeSet;
use std::convert::TryInto;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::{self, Write};
@ -298,9 +297,9 @@ fn generate_icon_files(settings: &Settings, data_dir: &PathBuf) -> crate::Result
if icon_path.extension() != Some(OsStr::new("png")) {
continue;
}
let decoder = PNGDecoder::new(File::open(&icon_path)?)?;
let width = decoder.dimensions().0.try_into()?;
let height = decoder.dimensions().1.try_into()?;
let decoder = PngDecoder::new(File::open(&icon_path)?)?;
let width = decoder.dimensions().0;
let height = decoder.dimensions().1;
let is_high_density = common::is_retina(&icon_path);
if !sizes.contains(&(width, height, is_high_density)) {
sizes.insert((width, height, is_high_density));
@ -333,8 +332,10 @@ fn generate_icon_files(settings: &Settings, data_dir: &PathBuf) -> crate::Result
if !sizes.contains(&(width, height, is_high_density)) {
sizes.insert((width, height, is_high_density));
let dest_path = get_dest_path(width, height, is_high_density);
let encoder = PNGEncoder::new(common::create_file(&dest_path)?);
encoder.encode(&icon.raw_pixels(), width, height, icon.color())?;
icon.write_to(
&mut common::create_file(&dest_path)?,
image::ImageOutputFormat::Png
)?;
}
}
}

View File

@ -375,7 +375,7 @@ fn create_icns_file(
}
for (icon, next_size_down, density) in images_to_resize {
let icon = icon.resize_exact(next_size_down, next_size_down, image::Lanczos3);
let icon = icon.resize_exact(next_size_down, next_size_down, image::imageops::FilterType::Lanczos3);
add_icon_to_family(icon, density, &mut family)?;
}
@ -395,14 +395,14 @@ fn create_icns_file(
/// Converts an image::DynamicImage into an icns::Image.
fn make_icns_image(img: image::DynamicImage) -> io::Result<icns::Image> {
let pixel_format = match img.color() {
image::ColorType::RGBA(8) => icns::PixelFormat::RGBA,
image::ColorType::RGB(8) => icns::PixelFormat::RGB,
image::ColorType::GrayA(8) => icns::PixelFormat::GrayAlpha,
image::ColorType::Gray(8) => icns::PixelFormat::Gray,
image::ColorType::Rgba8 => icns::PixelFormat::RGBA,
image::ColorType::Rgb8 => icns::PixelFormat::RGB,
image::ColorType::La8 => icns::PixelFormat::GrayAlpha,
image::ColorType::L8 => icns::PixelFormat::Gray,
_ => {
let msg = format!("unsupported ColorType: {:?}", img.color());
return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
}
};
icns::Image::from_data(pixel_format, img.width(), img.height(), img.raw_pixels())
icns::Image::from_data(pixel_format, img.width(), img.height(), img.to_bytes())
}

View File

@ -90,7 +90,7 @@ pub enum BuildArtifact {
Example(String),
}
#[derive(Clone, Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize, Default)]
struct BundleSettings {
// General settings:
name: Option<String>,
@ -200,20 +200,31 @@ impl Settings {
None
};
let cargo_settings = CargoSettings::load(&current_dir)?;
let tauri_config = super::tauri_config::get();
let package = match cargo_settings.package {
Some(package_info) => package_info,
None => bail!("No 'package' info found in 'Cargo.toml'"),
};
let workspace_dir = Settings::get_workspace_dir(&current_dir);
let target_dir = Settings::get_target_dir(&workspace_dir, &target, is_release, &build_artifact);
let bundle_settings = if let Some(bundle_settings) = package
.metadata
.as_ref()
.and_then(|metadata| metadata.bundle.as_ref())
{
bundle_settings.clone()
} else {
bail!("No [package.metadata.bundle] section in Cargo.toml");
let bundle_settings = match tauri_config {
Ok(config) => merge_settings(BundleSettings::default(), config.tauri.bundle),
Err(e) => {
let error_message = e.to_string();
if !error_message.contains("No such file or directory") {
bail!("Failed to read Tauri config: {}", error_message);
}
if let Some(bundle_settings) = package
.metadata
.as_ref()
.and_then(|metadata| metadata.bundle.as_ref())
{
bundle_settings.clone()
} else {
bail!("No [package.metadata.bundle] section in Cargo.toml");
}
}
};
let (bundle_settings, binary_name) = match build_artifact {
BuildArtifact::Main => (bundle_settings, package.name.clone()),
@ -235,12 +246,6 @@ impl Settings {
let bundle_settings = add_external_bin(bundle_settings)?;
let tauri_config = super::tauri_config::get();
let merged_bundle_settings = match tauri_config {
Ok(config) => merge_settings(bundle_settings, config.tauri.bundle),
Err(_) => bundle_settings,
};
Ok(Settings {
package,
package_type,
@ -251,7 +256,7 @@ impl Settings {
project_out_directory: target_dir,
binary_path,
binary_name,
bundle_settings: merged_bundle_settings,
bundle_settings,
})
}

View File

@ -1,5 +1,5 @@
# Webpack output
dist
/dist
# Logs
logs
@ -69,9 +69,3 @@ config.json
# rust compiled folders
target
# doing this because of how our tests currently (naively) drop the tauri.conf.json in that folder
# todo: needs a proper fic
tauri.conf.json
src-tauri

View File

@ -0,0 +1,32 @@
import tauri from './tauri'
/**
* @name openDialog
* @description Open a file/directory selection dialog
* @param {String} [options]
* @param {String} [options.filter]
* @param {String} [options.defaultPath]
* @param {Boolean} [options.multiple=false]
* @param {Boolean} [options.directory=false]
* @returns {Promise<String|String[]>} promise resolving to the select path(s)
*/
function open (options = {}) {
return tauri.openDialog(options)
}
/**
* @name save
* @description Open a file/directory save dialog
* @param {String} [options]
* @param {String} [options.filter]
* @param {String} [options.defaultPath]
* @returns {Promise<String>} promise resolving to the select path
*/
function save (options = {}) {
return tauri.saveDialog(options)
}
export {
open,
save
}

34
cli/tauri.js/api/event.js Normal file
View File

@ -0,0 +1,34 @@
import tauri from './tauri'
/**
* The event handler callback
* @callback eventCallback
* @param {object} event
* @param {string} event.type
* @param {any} [event.payload]
*/
/**
* listen to an event from the backend
*
* @param {string} event the event name
* @param {eventCallback} handler the event handler callback
*/
function listen (event, handler) {
tauri.listen(event, handler)
}
/**
* emits an event to the backend
*
* @param {string} event the event name
* @param {string} [payload] the event payload
*/
function emit (event, payload) {
tauri.emit(event, payload)
}
export {
listen,
emit
}

View File

@ -0,0 +1,30 @@
/**
* @typedef {number} BaseDirectory
*/
/**
* @enum {BaseDirectory}
*/
const Dir = {
Audio: 1,
Cache: 2,
Config: 3,
Data: 4,
LocalData: 5,
Desktop: 6,
Document: 7,
Download: 8,
Executable: 9,
Font: 10,
Home: 11,
Picture: 12,
Public: 13,
Runtime: 14,
Template: 15,
Video: 16,
Resource: 17,
App: 18
}
export {
Dir
}

View File

@ -0,0 +1,140 @@
import tauri from './tauri'
import { Dir } from './dir'
/**
* reads a file as text
*
* @param {string} filePath path to the file
* @param {object} [options] configuration object
* @param {BaseDirectory} [options.dir] base directory
* @return {Promise<string>}
*/
function readTextFile (filePath, options = {}) {
return tauri.readTextFile(filePath, options)
}
/**
* reads a file as binary
*
* @param {string} filePath path to the file
* @param {object} [options] configuration object
* @param {BaseDirectory} [options.dir] base directory
* @return {Promise<int[]>}
*/
function readBinaryFile (filePath, options = {}) {
return tauri.readBinaryFile(filePath, options)
}
/**
* writes a text file
*
* @param {object} file
* @param {string} file.path path of the file
* @param {string} file.contents contents of the file
* @param {object} [options] configuration object
* @param {BaseDirectory} [options.dir] base directory
* @return {Promise<void>}
*/
function writeFile (file, options = {}) {
return tauri.writeFile(file, options)
}
/**
* @typedef {object} FileEntry
* @property {string} path
* @property {boolean} is_dir
* @property {string} name
*/
/**
* list directory files
*
* @param {string} dir path to the directory to read
* @param {object} [options] configuration object
* @param {boolean} [options.recursive] whether to list dirs recursively or not
* @param {BaseDirectory} [options.dir] base directory
* @return {Promise<FileEntry[]>}
*/
function readDir (dir, options = {}) {
return tauri.readDir(dir, options)
}
/**
* Creates a directory
* If one of the path's parent components doesn't exist
* and the `recursive` option isn't set to true, it will be rejected
*
* @param {string} dir path to the directory to create
* @param {object} [options] configuration object
* @param {boolean} [options.recursive] whether to create the directory's parent components or not
* @param {BaseDirectory} [options.dir] base directory
* @return {Promise<void>}
*/
function createDir (dir, options = {}) {
return tauri.createDir(dir, options)
}
/**
* Removes a directory
* If the directory is not empty and the `recursive` option isn't set to true, it will be rejected
*
* @param {string} dir path to the directory to remove
* @param {object} [options] configuration object
* @param {boolean} [options.recursive] whether to remove all of the directory's content or not
* @param {BaseDirectory} [options.dir] base directory
* @return {Promise<void>}
*/
function removeDir (dir, options = {}) {
return tauri.removeDir(dir, options)
}
/**
* Copy file
*
* @param {string} source
* @param {string} destination
* @param {object} [options] configuration object
* @param {BaseDirectory} [options.dir] base directory
* @return {Promise<void>}
*/
function copyFile (source, destination, options = {}) {
return tauri.copyFile(source, destination, options)
}
/**
* Removes a file
*
* @param {string} file path to the file to remove
* @param {object} [options] configuration object
* @param {BaseDirectory} [options.dir] base directory
* @return {Promise<void>}
*/
function removeFile (file, options = {}) {
return tauri.removeFile(file, options)
}
/**
* Renames a file
*
* @param {string} oldPath
* @param {string} newPath
* @param {object} [options] configuration object
* @param {BaseDirectory} [options.dir] base directory
* @return {Promise<void>}
*/
function renameFile (oldPath, newPath, options = {}) {
return tauri.renameFile(oldPath, newPath, options)
}
export {
Dir,
readTextFile,
readBinaryFile,
writeFile,
readDir,
createDir,
removeDir,
copyFile,
removeFile,
renameFile
}

View File

@ -0,0 +1,3 @@
import tauri from './tauri'
export default tauri

View File

@ -0,0 +1,16 @@
import tauri from './tauri'
/**
* spawns a process
*
* @param {string} command the name of the cmd to execute e.g. 'mkdir' or 'node'
* @param {(string[]|string)} [args] command args
* @return {Promise<string>} promise resolving to the stdout text
*/
function execute (command, args) {
return tauri.execute(command, args)
}
export {
execute
}

View File

@ -2,7 +2,7 @@ const cache = {}
let initialized = false
const proxy = new Proxy({
__consume () {
__consume() {
for (const key in cache) {
if (key in window.tauri) {
const queue = cache[key]
@ -21,10 +21,11 @@ const proxy = new Proxy({
}
}
initialized = true
}
},
Dir: require('./fs/dir').Dir
}, {
get (obj, prop) {
if (prop === '__consume') {
get(obj, prop) {
if (prop === '__consume' || prop === 'Dir') {
return obj[prop]
}

View File

@ -0,0 +1,24 @@
import tauri from './tauri'
/**
* sets the window title
*
* @param {string} title the new title
*/
function setTitle (title) {
tauri.setTitle(title)
}
/**
* opens an URL on the user default browser
*
* @param {string} url the URL to open
*/
function open (url) {
tauri.open(url)
}
export {
setTitle,
open
}

View File

@ -0,0 +1,11 @@
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
node: 'current'
},
modules: 'commonjs'
}],
'@babel/preset-typescript'
]
};

View File

@ -3,7 +3,8 @@ const parseArgs = require('minimist')
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
d: 'debug'
d: 'debug',
t: 'target'
},
boolean: ['h', 'd']
})
@ -16,10 +17,17 @@ if (argv.help) {
$ tauri build
Options
--help, -h Displays this message
--debug, -d Builds with the debug flag
--target, -t Comma-separated list of target triples to build against
`)
process.exit(0)
}
const build = require('../dist/api/build')
build({ ctx: { debug: argv.debug } })
build({
ctx: {
debug: argv.debug,
target: argv.target
}
})

View File

@ -2,9 +2,10 @@ const parseArgs = require('minimist')
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help'
h: 'help',
e: 'exit-on-panic'
},
boolean: ['h']
boolean: ['h', 'e']
})
if (argv.help) {
@ -21,4 +22,8 @@ if (argv.help) {
const dev = require('../dist/api/dev')
dev()
dev({
ctx: {
exitOnPanic: argv['exit-on-panic']
}
})

View File

@ -28,15 +28,21 @@ module.exports = {
'<rootDir>/test/jest/__tests__/**/*.spec.js',
'<rootDir>/test/jest/__tests__/**/*.test.js'
],
moduleFileExtensions: ['js', 'json'],
testPathIgnorePatterns: [
'(build|dev).spec.js'
],
moduleFileExtensions: ['ts', 'js', 'json'],
moduleNameMapper: {
'^~/(.*)$': '<rootDir>/$1',
'^bin/(.*)$': '<rootDir>/bin/$1',
'^helpers/(.*)$': '<rootDir>/helpers/$1',
'^api/(.*)$': '<rootDir>/api/$1',
'^templates/(.*)$': '<rootDir>/templates/$1',
'^helpers/(.*)$': '<rootDir>/src/helpers/$1',
'^api/(.*)$': '<rootDir>/src/api/$1',
'^templates/(.*)$': '<rootDir>/src/templates/$1',
'^test/(.*)$': '<rootDir>/test/$1',
'../../package.json': '<rootDir>/package.json'
},
transform: {}
"transform": {
"templates[\\\\/](tauri|mutation-observer)\.js": "./test/jest/raw-loader-transformer.js",
"\\.(js|ts)$": "babel-jest"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "tauri",
"version": "0.4.5",
"version": "0.5.0",
"description": "Multi-binding collection of libraries and templates for building Tauri apps",
"bin": {
"tauri": "./bin/tauri.js"
@ -57,17 +57,20 @@
"imagemin-zopfli": "6.0.0",
"is-png": "2.0.0",
"isbinaryfile": "4.0.4",
"jsdom": "16.2.0",
"jsdom": "16.2.1",
"lodash": "4.17.15",
"minimist": "1.2.0",
"minimist": "1.2.4",
"ms": "2.1.2",
"png2icons": "2.0.1",
"read-chunk": "3.2.0",
"sharp": "0.24.1",
"sharp": "0.25.1",
"webpack-merge": "4.2.2",
"webpack-shell-plugin": "0.5.0"
},
"devDependencies": {
"@babel/core": "7.8.7",
"@babel/preset-env": "7.8.7",
"@babel/preset-typescript": "7.8.3",
"@types/cross-spawn": "6.0.1",
"@types/fs-extra": "8.1.0",
"@types/imagemin": "7.0.0",
@ -77,8 +80,9 @@
"@types/ms": "0.7.31",
"@types/sharp": "0.24.0",
"@types/webpack-merge": "4.1.5",
"@typescript-eslint/eslint-plugin": "2.21.0",
"@typescript-eslint/parser": "2.21.0",
"@typescript-eslint/eslint-plugin": "2.23.0",
"@typescript-eslint/parser": "2.23.0",
"babel-jest": "25.1.0",
"dotenv": "8.2.0",
"eslint": "6.8.0",
"eslint-config-standard-with-typescript": "14.0.0",
@ -89,15 +93,16 @@
"eslint-plugin-security": "1.4.0",
"eslint-plugin-standard": "4.0.1",
"husky": "4.2.3",
"is-running": "2.1.0",
"jest": "25.1.0",
"jest-mock-process": "1.3.2",
"lint-staged": "10.0.8",
"lockfile-lint": "4.0.0",
"promise": "8.0.3",
"lockfile-lint": "4.1.0",
"promise": "8.1.0",
"raw-loader": "4.0.0",
"ts-loader": "6.2.1",
"typescript": "3.8.3",
"webpack": "4.41.6",
"webpack": "4.42.0",
"webpack-cli": "3.3.11",
"webpack-node-externals": "1.7.2"
},

View File

@ -3,7 +3,12 @@ import merge from 'webpack-merge'
import Runner from '../runner'
import getTauriConfig from '../helpers/tauri-config'
module.exports = async (config: TauriConfig): Promise<void> => {
interface BuildResult {
promise: Promise<void>
runner: Runner
}
module.exports = (config: TauriConfig): BuildResult => {
const tauri = new Runner()
const tauriConfig = getTauriConfig(
merge(
@ -16,5 +21,8 @@ module.exports = async (config: TauriConfig): Promise<void> => {
) as TauriConfig
)
return tauri.build(tauriConfig)
return {
runner: tauri,
promise: tauri.build(tauriConfig)
}
}

View File

@ -3,7 +3,12 @@ import merge from 'webpack-merge'
import Runner from '../runner'
import getTauriConfig from '../helpers/tauri-config'
module.exports = async (config: TauriConfig): Promise<void> => {
interface DevResult {
promise: Promise<void>
runner: Runner
}
module.exports = (config: TauriConfig): DevResult => {
const tauri = new Runner()
const tauriConfig = getTauriConfig(
merge(
@ -17,5 +22,8 @@ module.exports = async (config: TauriConfig): Promise<void> => {
) as TauriConfig
)
return tauri.run(tauriConfig)
return {
runner: tauri,
promise: tauri.run(tauriConfig)
}
}

View File

@ -6,6 +6,7 @@ import os from 'os'
import path from 'path'
import { appDir, tauriDir } from '../helpers/app-paths'
import { TauriConfig } from './../types/config'
import nonWebpackRequire from '../helpers/non-webpack-require'
interface DirInfo {
path: string
@ -115,7 +116,7 @@ function printAppInfo(tauriDir: string): void {
return chalk.red('unset')
}
const configPath = path.join(tauriDir, 'tauri.conf.json')
const config = __non_webpack_require__(configPath) as TauriConfig
const config = nonWebpackRequire(configPath) as TauriConfig
printInfo({ key: ' mode', value: tauriMode(config) })
printInfo({
key: ' build-type',
@ -175,7 +176,8 @@ module.exports = () => {
printInfo({ key: 'App directory structure', section: true })
const tree = dirTree(appDir)
for (const artifact of tree.children ?? []) {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
for (const artifact of tree.children || []) {
if (artifact.type === 'folder') {
console.log(`/${artifact.name}`)
}

View File

@ -179,7 +179,7 @@ const tauricon = (exports.tauricon = {
return typeof image === 'object'
},
version: function() {
return __non_webpack_require__('../../package.json').version
return require('../../package.json').version
},
make: async function(
src: string = path.resolve(appDir, 'app-icon.png'),
@ -227,7 +227,8 @@ const tauricon = (exports.tauricon = {
try {
const pngImage = sharpSrc.resize(pvar[1], pvar[1])
if (pvar[2]) {
const rgb = hexToRgb(options.background_color) ?? {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const rgb = hexToRgb(options.background_color) || {
r: undefined,
g: undefined,
b: undefined
@ -292,7 +293,8 @@ const tauricon = (exports.tauricon = {
) {
let output
let block = false
const rgb = hexToRgb(options.background_color) ?? {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const rgb = hexToRgb(options.background_color) || {
r: undefined,
g: undefined,
b: undefined

View File

@ -1,11 +1,11 @@
import { ensureDirSync, writeFileSync } from 'fs-extra'
import { template } from 'lodash'
import { template } from 'lodash'
import path from 'path'
import { TauriConfig } from './types/config'
export const generate = (outDir: string, cfg: TauriConfig): void => {
// this MUST be from the templates repo
const apiTemplate = require('!!raw-loader!!../templates/tauri.js').default
const apiTemplate = require('../templates/tauri.js').default
const compiledApi = template(apiTemplate)
ensureDirSync(outDir)

View File

@ -1,21 +1,23 @@
import { existsSync } from 'fs'
import { join, normalize, resolve, sep } from 'path'
import logger from './logger'
const warn = logger('tauri', 'red')
const getAppDir = (): string => {
let dir = process.cwd()
let count = 0
// only go up three folders max
while (dir.length > 0 && dir.endsWith(sep) && count <= 2) {
if (existsSync(join(dir, 'tauri.conf.json'))) {
while (dir.length > 0 && !dir.endsWith(sep) && count <= 2) {
if (existsSync(join(dir, 'src-tauri', 'tauri.conf.json'))) {
return dir
}
count++
dir = normalize(join(dir, '..'))
}
// just return the current directory
return process.cwd()
warn('Couldn\'t find recognize the current folder as a part of a Tauri project')
process.exit(1)
}
const appDir = getAppDir()

View File

@ -0,0 +1,4 @@
// this function has been moved to a module so we can mock it
export default (path: string): any => {
return __non_webpack_require__(path)
}

View File

@ -19,7 +19,8 @@ export const spawn = (
// TODO: move to execa?
const runner = crossSpawn(cmd, params, {
stdio: 'inherit',
cwd
cwd,
env: process.env
})
runner.on('close', code => {
@ -29,7 +30,8 @@ export const spawn = (
log(`Command "${cmd}" failed with exit code: ${code}`)
}
onClose?.(code)
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
onClose && onClose(code)
})
return runner.pid
@ -42,7 +44,7 @@ export const spawnSync = (
cmd: string,
params: string[],
cwd: string,
onFail: () => void
onFail?: () => void
): void => {
log(`[sync] Running "${cmd} ${params.join(' ')}"`)
log()
@ -60,7 +62,8 @@ export const spawnSync = (
if (runner.status === null) {
warn(`⚠️ Please globally install "${cmd}"`)
}
onFail?.()
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
onFail && onFail()
process.exit(1)
}
}

View File

@ -4,6 +4,7 @@ import { TauriConfig } from 'types'
import merge from 'webpack-merge'
import logger from '../helpers/logger'
import * as appPaths from './app-paths'
import nonWebpackRequire from '../helpers/non-webpack-require'
const error = logger('ERROR:', 'red')
@ -20,8 +21,8 @@ const getTauriConfig = (cfg: Partial<TauriConfig>): TauriConfig => {
)
process.exit(1)
}
const tauriConf = __non_webpack_require__(tauriConfPath)
const pkg = __non_webpack_require__(pkgPath)
const tauriConf = nonWebpackRequire(tauriConfPath)
const pkg = nonWebpackRequire(pkgPath)
const config = merge(
{

View File

@ -1,26 +1,27 @@
import Inliner from '@tauri-apps/tauri-inliner'
import toml from '@tauri-apps/toml'
import toml, { JsonMap } from '@tauri-apps/toml'
import chokidar, { FSWatcher } from 'chokidar'
import { existsSync, readFileSync, writeFileSync } from 'fs-extra'
import { JSDOM } from 'jsdom'
import { debounce, template } from 'lodash'
import path from 'path'
import * as entry from './entry'
import { appDir, tauriDir } from './helpers/app-paths'
import { tauriDir } from './helpers/app-paths'
import logger from './helpers/logger'
import onShutdown from './helpers/on-shutdown'
import { spawn } from './helpers/spawn'
const getTauriConfig = require('./helpers/tauri-config')
import { spawn, spawnSync } from './helpers/spawn'
import { TauriConfig } from './types/config'
import getTauriConfig from './helpers/tauri-config'
const log = logger('app:tauri', 'green')
const warn = logger('app:tauri (template)', 'red')
const warn = logger('app:tauri (runner)', 'red')
class Runner {
pid: number
tauriWatcher?: FSWatcher
devPath?: string
killPromise?: Function
ranBeforeDevCommand?: boolean
constructor() {
this.pid = 0
@ -43,9 +44,15 @@ class Runner {
}
}
this.__manipulateToml(toml => {
this.__whitelistApi(cfg, toml)
})
if (!this.ranBeforeDevCommand && cfg.build.beforeDevCommand) {
this.ranBeforeDevCommand = true // prevent calling it twice on recursive call on our watcher
const [command, ...args] = cfg.build.beforeDevCommand.split(' ')
spawnSync(command, args, tauriDir)
}
const tomlContents = this.__getManifest()
this.__whitelistApi(cfg, tomlContents)
this.__rewriteManifest(tomlContents)
entry.generate(tauriDir, cfg)
@ -67,11 +74,27 @@ class Runner {
cargoArgs: ['run'].concat(
features.length ? ['--features', ...features] : []
),
dev: true
dev: true,
exitOnPanic: cfg.ctx.exitOnPanic
})
}
// Start watching for tauri app changes
// eslint-disable-next-line security/detect-non-literal-fs-filename
let tauriPaths: string[] = []
// @ts-ignore
if (tomlContents.dependencies.tauri.path) {
// @ts-ignore
const tauriPath = path.resolve(tauriDir, tomlContents.dependencies.tauri.path)
tauriPaths = [
tauriPath,
`${tauriPath}-api`,
`${tauriPath}-updater`,
`${tauriPath}-utils`
]
}
// eslint-disable-next-line security/detect-non-literal-fs-filename
this.tauriWatcher = chokidar
.watch(
@ -79,18 +102,25 @@ class Runner {
path.join(tauriDir, 'src'),
path.join(tauriDir, 'Cargo.toml'),
path.join(tauriDir, 'build.rs'),
path.join(appDir, 'tauri.conf.json')
],
path.join(tauriDir, 'tauri.conf.json'),
...tauriPaths
].concat(runningDevServer ? [] : [devPath]),
{
ignoreInitial: true
ignoreInitial: true,
ignored: runningDevServer ? null : path.join(devPath, 'index.tauri.html')
}
)
.on(
'change',
debounce((path: string) => {
this.__stopCargo()
debounce((changedPath: string) => {
if (changedPath.startsWith(path.join(tauriDir, 'target'))) {
return
}
(this.pid ? this.__stopCargo() : Promise.resolve())
.then(() => {
if (path.includes('tauri.conf.json')) {
const shouldTriggerRun = changedPath.includes('tauri.conf.json') ||
changedPath.startsWith(devPath)
if (shouldTriggerRun) {
this.run(getTauriConfig({ ctx: cfg.ctx })).catch(e => {
throw e
})
@ -111,9 +141,14 @@ class Runner {
}
async build(cfg: TauriConfig): Promise<void> {
this.__manipulateToml(toml => {
this.__whitelistApi(cfg, toml)
})
if (cfg.build.beforeBuildCommand) {
const [command, ...args] = cfg.build.beforeBuildCommand.split(' ')
spawnSync(command, args, tauriDir)
}
const tomlContents = this.__getManifest()
this.__whitelistApi(cfg, tomlContents)
this.__rewriteManifest(tomlContents)
entry.generate(tauriDir, cfg)
@ -135,8 +170,8 @@ class Runner {
.concat(target ? ['--target', target] : [])
})
if (cfg.ctx.debug || !cfg.ctx.targetName) {
// on debug mode or if no target specified,
if (!cfg.ctx.target) {
// if no target specified,
// build only for the current platform
await buildFn()
} else {
@ -160,15 +195,16 @@ class Runner {
reject(new Error('Could not find index.html in dist dir.'))
}
const rewriteHtml = (html: string, interceptor?: (dom: JSDOM) => void) => {
const rewriteHtml = (html: string, interceptor?: (dom: JSDOM) => void): void => {
const dom = new JSDOM(html)
const document = dom.window.document
if (interceptor !== undefined) {
interceptor(dom)
}
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if (!((cfg.ctx.dev && cfg.build.devPath.startsWith('http')) || cfg.tauri.embeddedServer.active)) {
const mutationObserverTemplate = require('!!raw-loader!!../templates/mutation-observer').default
const mutationObserverTemplate = require('../templates/mutation-observer').default
const compiledMutationObserver = template(mutationObserverTemplate)
const bodyMutationObserverScript = document.createElement('script')
@ -212,7 +248,7 @@ class Runner {
})
}
if (cfg.tauri.embeddedServer.active || !cfg.tauri.inliner.active) {
if ((!cfg.ctx.dev && cfg.tauri.embeddedServer.active) || !cfg.tauri.inliner.active) {
rewriteHtml(readFileSync(indexPath).toString(), domInterceptor)
resolve(inlinedAssets)
} else {
@ -236,22 +272,22 @@ class Runner {
this.tauriWatcher && this.tauriWatcher.close()
this.__stopCargo()
.then(resolve)
.catch(e => {
console.error(e)
})
.catch(reject)
})
}
async __runCargoCommand({
cargoArgs,
extraArgs,
dev = false
dev = false,
exitOnPanic = true
}: {
cargoArgs: string[]
extraArgs?: string[]
dev?: boolean
exitOnPanic?: boolean
}): Promise<void> {
return new Promise(resolve => {
return new Promise((resolve, reject) => {
this.pid = spawn(
'cargo',
@ -260,11 +296,20 @@ class Runner {
tauriDir,
code => {
if (dev && !exitOnPanic && code === 101) {
this.pid = 0
resolve()
return
}
if (code) {
warn()
warn('⚠️ [FAIL] Cargo CLI has failed')
warn()
reject(new Error('Cargo failed with status code ' + code.toString()))
process.exit(1)
} else if (!dev) {
resolve()
}
if (this.killPromise) {
@ -276,10 +321,14 @@ class Runner {
warn()
process.exit(0)
}
resolve()
}
)
resolve()
if (dev) {
resolve()
}
})
}
@ -295,18 +344,27 @@ class Runner {
return new Promise((resolve, reject) => {
this.killPromise = resolve
process.kill(pid)
try {
process.kill(pid)
} catch (e) {
reject(e)
}
})
}
__manipulateToml(callback: (tomlContents: object) => void): void {
const tomlPath = path.join(tauriDir, 'Cargo.toml')
const tomlFile = readFileSync(tomlPath)
// @ts-ignore
__getManifestPath(): string {
return path.join(tauriDir, 'Cargo.toml')
}
__getManifest(): JsonMap {
const tomlPath = this.__getManifestPath()
const tomlFile = readFileSync(tomlPath).toString()
const tomlContents = toml.parse(tomlFile)
return tomlContents
}
callback(tomlContents)
__rewriteManifest(tomlContents: JsonMap): void {
const tomlPath = this.__getManifestPath()
const output = toml.stringify(tomlContents)
writeFileSync(tomlPath, output)
}
@ -320,10 +378,15 @@ class Runner {
if (cfg.tauri.whitelist.all) {
tomlFeatures.push('all-api')
} else {
const toKebabCase = (value: string): string => {
return value.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/\s+/g, '-')
.toLowerCase()
}
const whitelist = Object.keys(cfg.tauri.whitelist).filter(
w => cfg.tauri.whitelist[String(w)] === true
w => cfg.tauri.whitelist[String(w)]
)
tomlFeatures.push(...whitelist)
tomlFeatures.push(...whitelist.map(toKebabCase))
}
if (cfg.tauri.edge.active) {

View File

@ -1,7 +1,9 @@
export default {
build: {
distDir: '../dist',
devPath: 'http://localhost:4000'
devPath: 'http://localhost:4000',
beforeDevCommand: '',
beforeBuildCommand: ''
},
ctx: {},
tauri: {
@ -15,7 +17,7 @@ export default {
resources: [],
externalBin: [],
copyright: '',
category: '',
category: 'DeveloperTool',
shortDescription: '',
longDescription: '',
deb: {
@ -31,7 +33,11 @@ export default {
all: true
},
window: {
title: 'Tauri App'
title: 'Tauri App',
width: 800,
height: 600,
resizable: true,
fullscreen: false
},
security: {
csp: "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"

View File

@ -62,8 +62,14 @@ Run \`tauri init --force template\` to overwrite.`)
if (!force) return false
}
const resolveTauriPath = (tauriPath: string): string => {
const resolvedPath = tauriPath.startsWith('/') || /^\S:/g.test(tauriPath)
? join(tauriPath, 'tauri') // we received a full path as argument
: join('..', tauriPath, 'tauri') // we received a relative path
return resolvedPath.replace(/\\/g, '/')
}
const tauriDep = tauriPath
? `{ path = "${join('..', tauriPath, 'tauri')}" }`
? `{ path = "${resolveTauriPath(tauriPath)}" }`
: null
try {

View File

@ -5,6 +5,8 @@ export interface TauriConfig {
build: {
distDir: string
devPath: string
beforeDevCommand?: string
beforeBuildCommand?: string
}
ctx: {
prod?: boolean
@ -12,6 +14,7 @@ export interface TauriConfig {
target: string
debug?: boolean
targetName: string
exitOnPanic?: boolean
}
bundle: {}
tauri: {

View File

@ -10,7 +10,9 @@ fn main() {
.invoke_handler(|_webview, arg| {
use cmd::Cmd::*;
match serde_json::from_str(arg) {
Err(_) => {}
Err(e) => {
Err(e.to_string())
}
Ok(command) => {
match command {
// definitions for your custom commands from Cmd here
@ -19,6 +21,7 @@ fn main() {
println!("{}", argument);
}
}
Ok(())
}
}
})

View File

@ -72,6 +72,33 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
/**
* @typedef {number} BaseDirectory
*/
/**
* @enum {BaseDirectory}
*/
var Dir = {
Audio: 1,
Cache: 2,
Config: 3,
Data: 4,
LocalData: 5,
Desktop: 6,
Document: 7,
Download: 8,
Executable: 9,
Font: 10,
Home: 11,
Picture: 12,
Public: 13,
Runtime: 14,
Template: 15,
Video: 16,
Resource: 17,
App: 18
}
<% if (ctx.dev) { %>
/**
* @name return __whitelistWarning
@ -103,6 +130,7 @@ var __reject = function () {
}
window.tauri = {
Dir: Dir,
<% if (ctx.dev) { %>
/**
* @name invoke
@ -153,7 +181,7 @@ window.tauri = {
this.invoke({
cmd: 'emit',
event: evt,
payload: payload || ''
payload: payload
});
<% } else { %>
<% if (ctx.dev) { %>
@ -212,14 +240,17 @@ window.tauri = {
* @description Accesses a non-binary file on the user's filesystem
* and returns the content. Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
<% } %>
readTextFile: function readTextFile(path) {
readTextFile: function readTextFile(path, options) {
<% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %>
return this.promisified({
cmd: 'readTextFile',
path: path
path: path,
options: options
});
<% } else { %>
<% if (ctx.dev) { %>
@ -235,14 +266,17 @@ window.tauri = {
* @description Accesses a binary file on the user's filesystem
* and returns the content. Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
<% } %>
readBinaryFile: function readBinaryFile(path) {
readBinaryFile: function readBinaryFile(path, options) {
<% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
return this.promisified({
cmd: 'readBinaryFile',
path: path
path: path,
options: options
});
<% } else { %>
<% if (ctx.dev) { %>
@ -260,17 +294,20 @@ window.tauri = {
* @param {Object} cfg
* @param {String} cfg.file
* @param {String|Binary} cfg.contents
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
*/
<% } %>
writeFile: function writeFile(cfg) {
writeFile: function writeFile(cfg, options) {
<% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %>
if (_typeof(cfg) === 'object') {
Object.freeze(cfg);
}
this.invoke({
return this.promisified({
cmd: 'writeFile',
file: cfg.file,
contents: cfg.contents
contents: cfg.contents,
options: options
});
<% } else { %>
<% if (ctx.dev) { %>
@ -282,22 +319,26 @@ window.tauri = {
<% if (ctx.dev) { %>
/**
* @name listFiles
* @description Get the files in a path.
* @name readDir
* @description Reads a directory
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {Boolean} [options.recursive]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
<% } %>
listFiles: function listFiles(path) {
<% if (tauri.whitelist.listFiles === true || tauri.whitelist.all === true) { %>
readDir: function readDir(path, options) {
<% if (tauri.whitelist.readDir === true || tauri.whitelist.all === true) { %>
return this.promisified({
cmd: 'listFiles',
path: path
cmd: 'readDir',
path: path,
options: options
});
<% } else { %>
<% if (ctx.dev) { %>
return __whitelistWarning('listDirs')
return __whitelistWarning('readDir')
<% } %>
return __reject()
<% } %>
@ -305,22 +346,134 @@ window.tauri = {
<% if (ctx.dev) { %>
/**
* @name listDirs
* @description Get the directories in a path.
* @name createDir
* @description Creates a directory
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {Boolean} [options.recursive]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
<% } %>
listDirs: function listDirs(path) {
<% if (tauri.whitelist.listDirs === true || tauri.whitelist.all === true) { %>
createDir: function createDir(path, options) {
<% if (tauri.whitelist.createDir === true || tauri.whitelist.all === true) { %>
return this.promisified({
cmd: 'listDirs',
path: path
cmd: 'createDir',
path: path,
options: options
});
<% } else { %>
<% if (ctx.dev) { %>
return __whitelistWarning('listDirs')
return __whitelistWarning('createDir')
<% } %>
return __reject()
<% } %>
},
<% if (ctx.dev) { %>
/**
* @name removeDir
* @description Removes a directory
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {Boolean} [options.recursive]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
<% } %>
removeDir: function removeDir(path, options) {
<% if (tauri.whitelist.removeDir === true || tauri.whitelist.all === true) { %>
return this.promisified({
cmd: 'removeDir',
path: path,
options: options
});
<% } else { %>
<% if (ctx.dev) { %>
return __whitelistWarning('removeDir')
<% } %>
return __reject()
<% } %>
},
<% if (ctx.dev) { %>
/**
* @name copyFile
* @description Copy file
* Permissions based on the app's PID owner
* @param {String} source
* @param {String} destination
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
<% } %>
copyFile: function copyFile(source, destination, options) {
<% if (tauri.whitelist.copyFile === true || tauri.whitelist.all === true) { %>
return this.promisified({
cmd: 'copyFile',
source: source,
destination: destination,
options: options
});
<% } else { %>
<% if (ctx.dev) { %>
return __whitelistWarning('copyFile')
<% } %>
return __reject()
<% } %>
},
<% if (ctx.dev) { %>
/**
* @name removeFile
* @description Removes a file
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
<% } %>
removeFile: function removeFile(path, options) {
<% if (tauri.whitelist.removeFile === true || tauri.whitelist.all === true) { %>
return this.promisified({
cmd: 'removeFile',
path: path,
options: options
});
<% } else { %>
<% if (ctx.dev) { %>
return __whitelistWarning('removeFile')
<% } %>
return __reject()
<% } %>
},
<% if (ctx.dev) { %>
/**
* @name renameFile
* @description Renames a file
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
<% } %>
renameFile: function renameFile(oldPath, newPath, options) {
<% if (tauri.whitelist.renameFile === true || tauri.whitelist.all === true) { %>
return this.promisified({
cmd: 'renameFile',
old_path: oldPath,
new_path: newPath,
options: options
});
<% } else { %>
<% if (ctx.dev) { %>
return __whitelistWarning('renameFile')
<% } %>
return __reject()
<% } %>
@ -398,24 +551,64 @@ window.tauri = {
<% } %>
},
bridge: function bridge(command, payload) {
<% if (tauri.whitelist.bridge === true || tauri.whitelist.all === true) { %>
if (_typeof(payload) === 'object') {
Object.freeze(payload);
}
return this.promisified({
cmd: 'bridge',
command: command,
payload: _typeof(payload) === 'object' ? [payload] : payload
});
<% if (ctx.dev) { %>
/**
* @name openDialog
* @description Open a file/directory selection dialog
* @param {String} [options]
* @param {String} [options.filter]
* @param {String} [options.defaultPath]
* @param {Boolean} [options.multiple=false]
* @param {Boolean} [options.directory=false]
* @returns {Promise<String|String[]>} promise resolving to the select path(s)
*/
<% } %>
openDialog: function openDialog(options) {
<% if (tauri.whitelist.openDialog === true || tauri.whitelist.all === true) { %>
var opts = options || {}
if (_typeof(options) === 'object') {
opts.default_path = opts.defaultPath
Object.freeze(options);
}
return this.promisified({
cmd: 'openDialog',
options: opts
});
<% } else { %>
<% if (ctx.dev) { %>
return __whitelistWarning('bridge')
<% if (ctx.dev) { %>
return __whitelistWarning('openDialog')
<% } %>
return __reject()
<% } %>
},
<% if (ctx.dev) { %>
/**
* @name saveDialog
* @description Open a file/directory save dialog
* @param {String} [options]
* @param {String} [options.filter]
* @param {String} [options.defaultPath]
* @returns {Promise<String>} promise resolving to the select path
*/
<% } %>
saveDialog: function saveDialog(options) {
<% if (tauri.whitelist.saveDialog === true || tauri.whitelist.all === true) { %>
var opts = options || {}
if (_typeof(options) === 'object') {
opts.default_path = opts.defaultPath
Object.freeze(options);
}
return this.promisified({
cmd: 'saveDialog',
options: opts
});
<% } else { %>
<% if (ctx.dev) { %>
return __whitelistWarning('saveDialog')
<% } %>
return __reject()
<% } %>
return __reject()
<% } %>
},
loadAsset: function loadAsset(assetName, assetType) {

View File

@ -0,0 +1,72 @@
const path = require('path')
const fixtureSetup = require('../fixtures/app-test-setup')
const appDir = path.join(fixtureSetup.fixtureDir, 'app')
const distDir = path.join(appDir, 'dist')
const spawn = require('helpers/spawn').spawn
function runBuildTest(tauriConfig) {
fixtureSetup.initJest('app')
const build = require('api/build')
return new Promise(async (resolve, reject) => {
try {
let success = false
const server = fixtureSetup.startServer(() => {
success = true
// wait for the app process to be killed
setTimeout(resolve, 2000)
})
const result = build(tauriConfig)
await result.promise
const artifactFolder = tauriConfig.ctx.debug ? 'debug' : 'release'
const artifactPath = path.resolve(appDir, `src-tauri/target/${artifactFolder}/app`)
const appPid = spawn(
process.platform === 'win32' ? `${artifactPath}.exe` : artifactPath.replace(`${artifactFolder}/app`, `${artifactFolder}/./app`),
[],
null
)
setTimeout(() => {
if (!success) {
server.close(() => {
try {
process.kill(appPid)
} catch {}
reject("App didn't reply")
})
}
}, 2500)
} catch (error) {
reject(error)
}
})
}
describe('Tauri Build', () => {
const build = {
devPath: distDir,
distDir: distDir
}
it.each`
mode | flag
${'embedded-server'} | ${'debug'}
${'embedded-server'} | ${'release'}
${'no-server'} | ${'debug'}
${'no-server'} | ${'release'}
`('works with the $mode $flag mode', ({ mode, flag }) => {
return runBuildTest({
build,
ctx: {
debug: flag === 'debug'
},
tauri: {
embeddedServer: {
active: mode === 'embedded-server'
}
}
})
})
})

View File

@ -0,0 +1,96 @@
const path = require('path')
const fixtureSetup = require('../fixtures/app-test-setup')
const distDir = path.resolve(fixtureSetup.fixtureDir, 'app', 'dist')
function startDevServer() {
const http = require('http')
const { statSync, createReadStream } = require('fs')
const app = http.createServer((req, res) => {
if (req.method === 'GET') {
if (req.url === '/') {
const indexPath = path.join(distDir, 'index.html')
const stat = statSync(indexPath)
res.writeHead(200, {
'Content-Type': 'text/html',
'Content-Length': stat.size
})
createReadStream(indexPath).pipe(res)
}
}
})
const port = 7001
const server = app.listen(port)
return {
server,
url: `http://localhost:${port}`
}
}
function runDevTest(tauriConfig) {
fixtureSetup.initJest('app')
const dev = require('api/dev')
return new Promise(async (resolve, reject) => {
try {
const { promise, runner } = dev(tauriConfig)
const isRunning = require('is-running')
let success = false
const checkIntervalId = setInterval(async () => {
if (!isRunning(runner.pid) && !success) {
server.close(() => reject("App didn't reply"))
}
}, 2000)
const server = fixtureSetup.startServer(async () => {
success = true
clearInterval(checkIntervalId)
// wait for the app process to be killed
setTimeout(async () => {
try {
await runner.stop()
} catch {}
resolve()
}, 2000)
})
await promise
} catch (error) {
reject(error)
}
})
}
describe('Tauri Dev', () => {
const build = {
distDir: distDir
}
const devServer = startDevServer()
it.each`
url
${devServer.url}
${distDir}
`('works with dev pointing to $url', ({ url }) => {
const runningDevServer = url.startsWith('http')
const promise = runDevTest({
build: {
...build,
devPath: url
},
ctx: {
debug: true,
dev: true
}
})
promise.then(() => {
if (runningDevServer) {
devServer.server.close()
}
})
return promise
})
})

View File

@ -1,4 +1,3 @@
// eslint-disable-next-line node/no-missing-require
const { tauri } = require('bin/tauri')
describe('[CLI] tauri.js', () => {
@ -27,6 +26,7 @@ describe('[CLI] tauri.js', () => {
it('will pass on an available command', async () => {
jest.spyOn(console, 'log')
jest.mock('fs')
tauri('init')
expect(console.log.mock.calls[0][0].split('.')[0]).toBe('[tauri]: running init')
jest.clearAllMocks()

View File

@ -1,4 +1,7 @@
const tauricon = require('~/dist/api/tauricon.js')
const appTestSetup = require('../fixtures/app-test-setup')
appTestSetup.initJest('app')
const tauricon = require('api/tauricon')
describe('[CLI] tauri-icon internals', () => {
it('tells you the version', () => {

View File

@ -0,0 +1,38 @@
const fixtureSetup = require('../fixtures/app-test-setup')
const { resolve } = require('path')
const { rmdirSync, existsSync, writeFileSync, readFileSync } = require('fs')
describe('[CLI] tauri.js template', () => {
it('init a project and builds it', done => {
const cwd = process.cwd()
try {
const fixturePath = resolve(__dirname, '../fixtures/empty')
const tauriFixturePath = resolve(fixturePath, 'src-tauri')
fixtureSetup.initJest('empty')
process.chdir(fixturePath)
const init = require('api/init')
init({
directory: process.cwd(),
force: true,
tauriPath: resolve(__dirname, '../../../../..')
})
process.chdir(tauriFixturePath)
const manifestPath = resolve(tauriFixturePath, 'Cargo.toml')
const manifestFile = readFileSync(manifestPath).toString()
writeFileSync(manifestPath, `workspace = { }\n\n${manifestFile}`)
} catch (e) {
done(e)
}
const build = require('api/build')
build().promise.then(() => {
process.chdir(cwd)
done()
}).catch(done)
})
})

View File

@ -0,0 +1,71 @@
const path = require('path')
const process = require('process')
const mockFixtureDir = path.resolve(__dirname, '../fixtures')
module.exports.fixtureDir = mockFixtureDir
module.exports.initJest = (mockFixture) => {
jest.setTimeout(720000)
jest.mock('helpers/non-webpack-require', () => {
return path => {
const value = require('fs').readFileSync(path).toString()
if (path.endsWith('.json')) {
return JSON.parse(value)
}
return value
}
})
jest.mock('helpers/app-paths', () => {
const path = require('path')
const appDir = path.join(mockFixtureDir, mockFixture)
const tauriDir = path.join(appDir, 'src-tauri')
return {
appDir,
tauriDir,
resolve: {
app: dir => path.join(appDir, dir),
tauri: dir => path.join(tauriDir, dir)
}
}
})
jest.spyOn(process, 'exit').mockImplementation(() => {})
}
module.exports.startServer = (onReply) => {
const http = require('http')
const app = http.createServer((req, res) => {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Request-Method', '*')
res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET')
res.setHeader('Access-Control-Allow-Headers', '*')
if (req.method === 'OPTIONS') {
res.writeHead(200)
res.end()
return
}
if (req.method === 'POST') {
if (req.url === '/reply') {
let body = ''
req.on('data', chunk => {
body += chunk.toString()
})
req.on('end', () => {
expect(JSON.parse(body)).toStrictEqual({
msg: 'TEST'
})
server.close(onReply)
})
}
}
})
const port = 7000
const server = app.listen(port)
return server
}

View File

@ -0,0 +1 @@
dist/index.tauri.html

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<body>
<script>
function notify(route, args) {
var xhr = new XMLHttpRequest()
xhr.open('POST', 'http://localhost:7000/' + route)
xhr.setRequestHeader('Content-type', 'application/json')
xhr.send(JSON.stringify(args))
}
window.onTauriInit = function () {
window.tauri.listen('reply', function (res) {
notify('reply', res.payload)
})
window.tauri.emit('hello')
}
setTimeout(function () {
window.tauri.invoke({ cmd: 'exit' })
}, 1000)
</script>
</body>
</html>

View File

@ -0,0 +1,54 @@
const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors())
app.use(express.json())
const port = 7000
let appPid
app.post('/reply', (req, res) => {
if (req.body && req.body.msg !== 'TEST') {
throw new Error(`unexpected reply ${JSON.stringify(req.body)}`)
}
console.log('App event replied')
exit(0)
})
const server = app.listen(port, () => console.log(`Test listening on port ${port}!`))
const exit = code => {
server.close()
process.kill(appPid)
process.exit(code)
}
const path = require('path')
const dist = path.resolve(__dirname, 'dist')
const build = require('../cli/tauri.js/dist/api/build')
build({
build: {
devPath: dist
},
ctx: {
debug: true
},
tauri: {
embeddedServer: {
active: true
}
}
}).then(() => {
const spawn = require('../cli/tauri.js/dist/helpers/spawn').spawn
const artifactPath = path.resolve(__dirname, 'src-tauri/target/debug/app')
appPid = spawn(
process.platform === 'win32' ? `${artifactPath}.exe` : artifactPath.replace('debug/app', 'debug/./app'),
[],
null
)
// if it didn't reply, throw an error
setTimeout(() => {
throw new Error("App didn't reply")
}, 2000)
})

View File

@ -0,0 +1,22 @@
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"tauri:prod": "tauri",
"tauri:source": "node ../../../../bin/tauri",
"tauri:source:init": "yarn tauri:source init --tauriPath ..",
"tauri:prod:init": "yarn tauri:prod init",
"tauri:source:dev": "yarn tauri:source dev",
"tauri:prod:dev": "yarn tauri:prod dev",
"tauri:source:build": "yarn tauri:source build",
"tauri:prod:build": "yarn tauri:prod build"
},
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1"
}
}

View File

@ -0,0 +1,14 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
tauri.js
config.json
bundle.json

View File

@ -0,0 +1,39 @@
workspace = { }
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
author = [ "Daniel Thompson-Yvetot" ]
license = ""
repository = ""
default-run = "app"
edition = "2018"
[package.metadata.bundle]
identifier = "com.tauri.dev"
icon = [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
[dependencies]
serde_json = "1.0.48"
serde = "1.0"
serde_derive = "1.0"
tiny_http = "0.6"
phf = "0.8.0"
includedir = "0.5.0"
tauri = { path = "../../../../../../../tauri", features = [ "all-api", "edge" ] }
[features]
dev-server = [ "tauri/dev-server" ]
embedded-server = [ "tauri/embedded-server" ]
no-server = [ "tauri/no-server" ]
[[bin]]
name = "app"
path = "src/main.rs"

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -0,0 +1,13 @@
max_width = 100
hard_tabs = false
tab_spaces = 2
newline_style = "Auto"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
edition = "2018"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true

View File

@ -0,0 +1,8 @@
#[derive(Deserialize)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
// your custom commands
// multiple arguments are allowed
// note that rename_all = "camelCase": you need to use "myCustomCommand" on JS
Exit { },
}

View File

@ -0,0 +1,31 @@
mod cmd;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
fn main() {
tauri::AppBuilder::new()
.setup(|_webview| {
let handle = _webview.handle();
tauri::event::listen(String::from("hello"), move |_| {
tauri::event::emit(&handle, String::from("reply"), "{ msg: 'TEST' }".to_string());
});
})
.invoke_handler(|webview, arg| {
use cmd::Cmd::*;
match serde_json::from_str(arg) {
Err(_) => {}
Ok(command) => {
match command {
// definitions for your custom commands from Cmd here
Exit { } => {
webview.exit();
}
}
}
}
})
.build()
.run();
}

View File

@ -0,0 +1,7 @@
{
"tauri": {
"whitelist": {
"all": true
}
}
}

View File

@ -0,0 +1,381 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
dependencies:
mime-types "~2.1.24"
negotiator "0.6.2"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
body-parser@1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
dependencies:
bytes "3.1.0"
content-type "~1.0.4"
debug "2.6.9"
depd "~1.1.2"
http-errors "1.7.2"
iconv-lite "0.4.24"
on-finished "~2.3.0"
qs "6.7.0"
raw-body "2.4.0"
type-is "~1.6.17"
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
dependencies:
safe-buffer "5.1.2"
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
cookie@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
dependencies:
accepts "~1.3.7"
array-flatten "1.1.1"
body-parser "1.19.0"
content-disposition "0.5.3"
content-type "~1.0.4"
cookie "0.4.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "~1.1.2"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "~1.1.2"
fresh "0.5.2"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
proxy-addr "~2.0.5"
qs "6.7.0"
range-parser "~1.2.1"
safe-buffer "5.1.2"
send "0.17.1"
serve-static "1.14.1"
setprototypeof "1.1.1"
statuses "~1.5.0"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
finalhandler@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
escape-html "~1.0.3"
on-finished "~2.3.0"
parseurl "~1.3.3"
statuses "~1.5.0"
unpipe "~1.0.0"
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
http-errors@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
dependencies:
depd "~1.1.2"
inherits "2.0.3"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-errors@~1.7.2:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
dependencies:
depd "~1.1.2"
inherits "2.0.4"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
inherits@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ipaddr.js@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
mime-db@1.42.0:
version "1.42.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac"
integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==
mime-types@~2.1.24:
version "2.1.25"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437"
integrity sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==
dependencies:
mime-db "1.42.0"
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
object-assign@^4:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
dependencies:
ee-first "1.1.1"
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
proxy-addr@~2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
dependencies:
forwarded "~0.1.2"
ipaddr.js "1.9.0"
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
dependencies:
bytes "3.1.0"
http-errors "1.7.2"
iconv-lite "0.4.24"
unpipe "1.0.0"
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
dependencies:
debug "2.6.9"
depd "~1.1.2"
destroy "~1.0.4"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "~1.7.2"
mime "1.6.0"
ms "2.1.1"
on-finished "~2.3.0"
range-parser "~1.2.1"
statuses "~1.5.0"
serve-static@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.17.1"
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
dependencies:
media-typer "0.3.0"
mime-types "~2.1.24"
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=

View File

@ -0,0 +1,2 @@
src-tauri
dist/index.tauri.html

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<div></div>
</body>
</html>

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,3 @@
module.exports = {
process: content => `module.exports = {default: ${JSON.stringify(content)}}`
}

View File

@ -7,8 +7,9 @@ module.exports = {
'api/dev': './src/api/dev.ts',
'api/init': './src/api/init.ts',
'api/tauricon': './src/api/tauricon.ts',
'api/info': './src/api/info.ts',
'helpers/tauri-config': './src/helpers/tauri-config.ts',
'api/info': './src/api/info.ts'
'helpers/spawn': './src/helpers/spawn.ts'
},
mode: process.env.NODE_ENV || 'development',
devtool: 'source-map',
@ -18,6 +19,10 @@ module.exports = {
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /templates[\\/](tauri|mutation-observer)\.js/,
use: 'raw-loader'
}
]
},

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-api"
version = "0.4.2"
version = "0.5.0"
authors = ["Lucas Fernandes Gonçalves Nogueira <lucas@quasar.dev>", "Daniel Thompson-Yvetot <denjell@sfosc.org>", "Tensor Programming <tensordeveloper@gmail.com>"]
license = "MIT"
homepage = "https://tauri.studio"
@ -11,6 +11,7 @@ exclude = ["test/fixture/**"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
dirs = "2.0.2"
ignore = "0.4.11"
zip = "0.5.4"
@ -21,7 +22,9 @@ either = "1.5.3"
tar = "0.4"
flate2 = "1"
error-chain = "0.12"
tauri-utils = {version = "0.4", path = "../tauri-utils"}
rand = "0.7"
nfd = "0.0.4"
tauri-utils = {version = "0.5", path = "../tauri-utils"}
[dev-dependencies]
quickcheck = "0.9.2"

33
tauri-api/src/dialog.rs Normal file
View File

@ -0,0 +1,33 @@
use nfd::{DialogType, open_dialog};
pub use nfd::Response;
fn open_dialog_internal(dialog_type: DialogType, filter: Option<String>, default_path: Option<String>) -> crate::Result<Response> {
open_dialog(filter.as_deref(), default_path.as_deref(), dialog_type)
.map_err(|err| crate::Error::with_chain(err, "open dialog failed"))
.and_then(|response| {
match response {
Response::Cancel => Err(crate::Error::from("user cancelled")),
_ => Ok(response)
}
})
}
/// Open single select file dialog
pub fn select(filter_list: Option<String>, default_path: Option<String>) -> crate::Result<Response> {
open_dialog_internal(DialogType::SingleFile, filter_list, default_path)
}
/// Open mulitple select file dialog
pub fn select_multiple(filter_list: Option<String>, default_path: Option<String>) -> crate::Result<Response> {
open_dialog_internal(DialogType::MultipleFiles, filter_list, default_path)
}
/// Open save dialog
pub fn save_file(filter_list: Option<String>, default_path: Option<String>) -> crate::Result<Response> {
open_dialog_internal(DialogType::SaveFile, filter_list, default_path)
}
/// Open pick folder dialog
pub fn pick_folder(default_path: Option<String>) -> crate::Result<Response> {
open_dialog_internal(DialogType::PickFolder, None, default_path)
}

View File

@ -44,7 +44,7 @@ pub fn walk_dir(path_copy: String) -> crate::Result<Vec<DiskEntry>> {
pub fn list_dir_contents(dir_path: String) -> crate::Result<Vec<DiskEntry>> {
fs::read_dir(dir_path)
.map_err(|err| crate::Error::with_chain(err, "read string failed"))
.map_err(|err| crate::Error::with_chain(err, "read dir failed"))
.and_then(|paths| {
let mut dirs: Vec<DiskEntry> = vec![];
for path in paths {

View File

@ -8,6 +8,9 @@ pub mod dir;
pub mod file;
pub mod rpc;
pub mod version;
pub mod tcp;
pub mod dialog;
pub mod path;
pub use tauri_utils::*;
@ -33,5 +36,9 @@ error_chain! {
description("File function Error")
display("File Error: {}", t)
}
Path(t: String) {
description("Path function Error")
display("Path Error: {}", t)
}
}
}

163
tauri-api/src/path.rs Normal file
View File

@ -0,0 +1,163 @@
use std::path::PathBuf;
use serde_repr::{Serialize_repr, Deserialize_repr};
#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
#[repr(u16)]
pub enum BaseDirectory {
Audio = 1,
Cache,
Config,
Data,
LocalData,
Desktop,
Document,
Download,
Executable,
Font,
Home,
Picture,
Public,
Runtime,
Template,
Video,
Resource,
App,
}
pub fn resolve_path(path: String, dir: Option<BaseDirectory>) -> crate::Result<String> {
if let Some(base_dir) = dir {
let base_dir_path = match base_dir {
BaseDirectory::Audio => audio_dir(),
BaseDirectory::Cache => cache_dir(),
BaseDirectory::Config => config_dir(),
BaseDirectory::Data => data_dir(),
BaseDirectory::LocalData => local_data_dir(),
BaseDirectory::Desktop => desktop_dir(),
BaseDirectory::Document => document_dir(),
BaseDirectory::Download => download_dir(),
BaseDirectory::Executable => executable_dir(),
BaseDirectory::Font => font_dir(),
BaseDirectory::Home => home_dir(),
BaseDirectory::Picture => picture_dir(),
BaseDirectory::Public => public_dir(),
BaseDirectory::Runtime => runtime_dir(),
BaseDirectory::Template => template_dir(),
BaseDirectory::Video => video_dir(),
BaseDirectory::Resource => resource_dir(),
BaseDirectory::App => app_dir(),
};
if let Some(mut base_dir_path_value) = base_dir_path {
base_dir_path_value.push(path);
Ok(base_dir_path_value.to_string_lossy().to_string())
} else {
Err(crate::Error::from(crate::ErrorKind::Path("unable to determine base dir path".to_string())))
}
} else {
Ok(path)
}
}
// Returns the path to the user's audio directory.
pub fn audio_dir() -> Option<PathBuf> {
dirs::audio_dir()
}
// Returns the path to the user's cache directory.
pub fn cache_dir() -> Option<PathBuf> {
dirs::cache_dir()
}
// Returns the path to the user's config directory.
pub fn config_dir() -> Option<PathBuf> {
dirs::config_dir()
}
// Returns the path to the user's data directory.
pub fn data_dir() -> Option<PathBuf> {
dirs::data_dir()
}
// Returns the path to the user's local data directory.
pub fn local_data_dir() -> Option<PathBuf> {
dirs::data_local_dir()
}
// Returns the path to the user's desktop directory.
pub fn desktop_dir() -> Option<PathBuf> {
dirs::desktop_dir()
}
// Returns the path to the user's document directory.
pub fn document_dir() -> Option<PathBuf> {
dirs::document_dir()
}
// Returns the path to the user's download directory.
pub fn download_dir() -> Option<PathBuf> {
dirs::download_dir()
}
// Returns the path to the user's executable directory.
pub fn executable_dir() -> Option<PathBuf> {
dirs::executable_dir()
}
// Returns the path to the user's font directory.
pub fn font_dir() -> Option<PathBuf> {
dirs::font_dir()
}
// Returns the path to the user's home directory.
pub fn home_dir() -> Option<PathBuf> {
dirs::home_dir()
}
// Returns the path to the user's picture directory.
pub fn picture_dir() -> Option<PathBuf> {
dirs::picture_dir()
}
// Returns the path to the user's public directory.
pub fn public_dir() -> Option<PathBuf> {
dirs::public_dir()
}
// Returns the path to the user's runtime directory.
pub fn runtime_dir() -> Option<PathBuf> {
dirs::runtime_dir()
}
// Returns the path to the user's template directory.
pub fn template_dir() -> Option<PathBuf> {
dirs::template_dir()
}
// Returns the path to the user's video dir
pub fn video_dir() -> Option<PathBuf> {
dirs::video_dir()
}
pub fn resource_dir() -> Option<PathBuf> {
crate::platform::resource_dir().ok()
}
fn app_name() -> crate::Result<String> {
let exe = std::env::current_exe()?;
let app_name = exe
.file_name().expect("failed to get exe filename")
.to_string_lossy();
Ok(app_name.to_string())
}
pub fn app_dir() -> Option<PathBuf> {
dirs::config_dir()
.and_then(|mut dir| {
if let Ok(app_name) = app_name() {
dir.push(app_name);
Some(dir)
} else {
None
}
})
}

View File

@ -17,8 +17,5 @@ pub fn get_available_port() -> Option<u16> {
}
pub fn port_is_available(port: u16) -> bool {
match TcpListener::bind(("127.0.0.1", port)) {
Ok(_) => true,
Err(_) => false,
}
TcpListener::bind(("127.0.0.1", port)).is_ok()
}

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-updater"
version = "0.4.1"
version = "0.4.2"
authors = ["Lucas Fernandes Gonçalves Nogueira <lucas@quasar.dev>", "Daniel Thompson-Yvetot <denjell@sfosc.org>", "Tensor Programming <tensordeveloper@gmail.com>"]
license = "MIT"
homepage = "https://tauri.studio"
@ -17,5 +17,5 @@ serde = "1.0"
zip = "0.5.3"
tempdir = "0.3"
error-chain = "0.12.1"
tauri-api = { version = "0.4", path = "../tauri-api" }
tauri-utils = { version = "0.4", path = "../tauri-utils" }
tauri-api = { version = "0.5", path = "../tauri-api" }
tauri-utils = { version = "0.5", path = "../tauri-utils" }

View File

@ -1,6 +1,6 @@
[package]
name = "tauri-utils"
version = "0.4.1"
version = "0.5.0"
authors = ["Lucas Fernandes Gonçalves Nogueira <lucas@quasar.dev>", "Daniel Thompson-Yvetot <denjell@sfosc.org>", "Tensor Programming <tensordeveloper@gmail.com>"]
license = "MIT"
homepage = "https://tauri.studio"

View File

@ -1,6 +1,6 @@
[package]
name = "tauri"
version = "0.4.3"
version = "0.5.0"
authors = ["Lucas Fernandes Gonçalves Nogueira <lucas@quasar.dev>", "Daniel Thompson-Yvetot <denjell@sfosc.org>", "Tensor Programming <tensordeveloper@gmail.com>"]
license = "MIT"
homepage = "https://tauri.studio"
@ -12,25 +12,28 @@ exclude = ["test/fixture/**"]
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
rand = "0.7"
web-view = "0.6.0"
webview-sys = "=0.5.0"
web-view = "=0.6.2"
tauri_includedir = "0.5.0"
phf = "0.8.0"
base64 = "0.11.0"
base64 = "0.12.0"
webbrowser = "0.5.2"
lazy_static = "1.4.0"
tiny_http = "0.6"
threadpool = "1.7"
uuid = { version = "0.8.1", features = ["v4"] }
error-chain = "0.12.1"
error-chain = "0.12.2"
tauri-api = { version = "0.4", path = "../tauri-api" }
tauri-api = { version = "0.5", path = "../tauri-api" }
[build-dependencies]
tauri_includedir_codegen = "0.5.2"
[dev-dependencies]
proptest = "0.9.5"
serde_json = "1.0"
tauri = {path = ".", features = [ "all-api", "edge" ]}
serde = { version = "1.0", features = [ "derive" ] }
[features]
edge = ["web-view/edge"]
@ -38,13 +41,27 @@ dev-server = []
embedded-server = []
no-server = []
all-api = []
readTextFile = []
readBinaryFile = []
writeFile = []
listFiles = []
listDirs = []
setTitle = []
read-text-file = []
read-binary-file = []
write-file = []
read-dir = []
copy-file = []
create-dir = []
remove-dir = []
remove-file = []
rename-file = []
set-title = []
execute = []
open = []
event = []
updater = []
open-dialog = []
save-dialog = []
[package.metadata.docs.rs]
features = ["dev-server", "all-api"]
[[example]]
name = "communication"
path = "examples/communication/src-tauri/src/main.rs"

View File

@ -0,0 +1,23 @@
document.getElementById('log').addEventListener('click', function () {
window.tauri.invoke({
cmd: 'logOperation',
event: 'tauri-click',
payload: 'this payload is optional because we used Option in Rust'
})
})
document.getElementById('request').addEventListener('click', function () {
window.tauri.promisified({
cmd: 'performRequest',
endpoint: 'dummy endpoint arg',
body: {
id: 5,
name: 'test'
}
}).then(registerResponse).catch(registerResponse)
})
document.getElementById('event').addEventListener('click', function () {
window.tauri.emit('js-event', 'this is the payload string')
})

View File

@ -0,0 +1,20 @@
var defaultPathInput = document.getElementById('dialog-default-path')
var filterInput = document.getElementById('dialog-filter')
var multipleInput = document.getElementById('dialog-multiple')
var directoryInput = document.getElementById('dialog-directory')
document.getElementById('open-dialog').addEventListener('click', function () {
window.tauri.openDialog({
defaultPath: defaultPathInput.value || null,
filter: filterInput.value || null,
multiple: multipleInput.checked,
directory: directoryInput.checked
}).then(registerResponse).catch(registerResponse)
})
document.getElementById('save-dialog').addEventListener('click', function () {
window.tauri.saveDialog({
defaultPath: defaultPathInput.value || null,
filter: filterInput.value || null
}).then(registerResponse).catch(registerResponse)
})

54
tauri/examples/communication/dist/fs.js vendored Normal file
View File

@ -0,0 +1,54 @@
var dirSelect = document.getElementById('dir')
function getDir () {
return dirSelect.value ? parseInt(dir.value) : null
}
function arrayBufferToBase64(buffer, callback) {
var blob = new Blob([buffer], {
type: 'application/octet-binary'
})
var reader = new FileReader()
reader.onload = function (evt) {
var dataurl = evt.target.result
callback(dataurl.substr(dataurl.indexOf(',') + 1))
}
reader.readAsDataURL(blob)
}
var pathInput = document.getElementById('path-to-read')
addClickEnterHandler(
document.getElementById('read'),
pathInput,
function () {
var pathToRead = pathInput.value
var isFile = pathToRead.match(/\S+\.\S+$/g)
var opts = { dir: getDir() }
var promise = isFile ? window.tauri.readBinaryFile(pathToRead, opts) : window.tauri.readDir(pathToRead, opts)
promise.then(function (response) {
if (isFile) {
if (pathToRead.includes('.png') || pathToRead.includes('.jpg')) {
arrayBufferToBase64(new Uint8Array(response), function (base64) {
var src = 'data:image/png;base64,' + base64
registerResponse('<img src="' + src + '"></img>')
})
} else {
var value = String.fromCharCode.apply(null, response)
registerResponse('<textarea id="file-response" style="height: 400px"></textarea><button id="file-save">Save</button>')
var fileInput = document.getElementById('file-response')
fileInput.value = value
document.getElementById('file-save').addEventListener('click', function () {
window.tauri.writeFile({
file: pathToRead,
contents: fileInput.value
}, {
dir: getDir()
}).catch(registerResponse)
})
}
} else {
registerResponse(response)
}
}).catch(registerResponse)
}
)

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html>
<body>
<div>
<button id="log">Call Log API</button>
<button id="request">Call Request (async) API</button>
<button id="event">Send event to Rust</button>
</div>
<div style="margin-top: 24px">
<select id="dir">
<option value="">None</option>
</select>
<input id="path-to-read" placeholder="Type the path to read...">
<button id="read">Read</button>
</div>
<div style="margin-top: 24px">
<input id="url" value="https://tauri.studio">
<button id="open-url">Open URL</button>
</div>
<div style="margin-top: 24px">
<input id="title" value="Awesome Tauri Example!">
<button id="set-title">Set title</button>
</div>
<div style="margin-top: 24px">
<input id="dialog-default-path" placeholder="Default path">
<input id="dialog-filter" placeholder="Extensions filter">
<div>
<input type="checkbox" id="dialog-multiple">
<label>Multiple</label>
</div>
<div>
<input type="checkbox" id="dialog-directory">
<label>Directory</label>
</div>
<button id="open-dialog">Open dialog</button>
<button id="save-dialog">Open save dialog</button>
</div>
<div id="response"></div>
<script>
function registerResponse (response) {
document.getElementById('response').innerHTML = typeof response === 'object'
? JSON.stringify(response)
: response
}
function addClickEnterHandler (button, input, handler) {
button.addEventListener('click', handler)
input.addEventListener('keyup', function (e) {
if (e.keyCode === 13) {
handler()
}
})
}
window.onTauriInit = function () {
window.tauri.listen('rust-event', function (res) {
document.getElementById('response').innerHTML = JSON.stringify(res)
})
var dirSelect = document.getElementById('dir')
for (var key in window.tauri.Dir) {
var value = window.tauri.Dir[key]
var opt = document.createElement("option")
opt.value = value
opt.innerHTML = key
dirSelect.appendChild(opt)
}
}
</script>
<script src="communication.js"></script>
<script src="fs.js"></script>
<script src="window.js"></script>
<script src="dialog.js"></script>
</body>
</html>

View File

@ -0,0 +1,594 @@
<!DOCTYPE html><html><head><meta http-equiv="Content-Security-Policy" content="default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"></head><body><script>/* eslint-disable */
/**
* * THIS FILE IS GENERATED AUTOMATICALLY.
* DO NOT EDIT.
*
* Please whitelist these API functions in tauri.conf.json
*
**/
/**
* @module tauri
* @description This API interface makes powerful interactions available
* to be run on client side applications. They are opt-in features, and
* must be enabled in tauri.conf.json
*
* Each binding MUST provide these interfaces in order to be compliant,
* and also whitelist them based upon the developer's settings.
*/
// polyfills
if (!String.prototype.startsWith) {
String.prototype.startsWith = function (searchString, position) {
position = position || 0
return this.substr(position, searchString.length) === searchString
}
}
// makes the window.external.invoke API available after window.location.href changes
switch (navigator.platform) {
case "Macintosh":
case "MacPPC":
case "MacIntel":
case "Mac68K":
window.external = this
invoke = function (x) {
webkit.messageHandlers.invoke.postMessage(x);
}
break;
case "Windows":
case "WinCE":
case "Win32":
case "Win64":
break;
default:
window.external = this
invoke = function (x) {
window.webkit.messageHandlers.external.postMessage(x);
}
break;
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1)
}
var uid = function () {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4()
}
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
/**
* @typedef {number} BaseDirectory
*/
/**
* @enum {BaseDirectory}
*/
var Dir = {
Audio: 1,
Cache: 2,
Config: 3,
Data: 4,
LocalData: 5,
Desktop: 6,
Document: 7,
Download: 8,
Executable: 9,
Font: 10,
Home: 11,
Picture: 12,
Public: 13,
Runtime: 14,
Template: 15,
Video: 16,
Resource: 17,
App: 18
}
/**
* @name return __whitelistWarning
* @description Present a stylish warning to the developer that their API
* call has not been whitelisted in tauri.conf.json
* @param {String} func - function name to warn
* @private
*/
var __whitelistWarning = function (func) {
console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n whitelist: { \n ' + func + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func, 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ')
return __reject()
}
/**
* @name __reject
* @description generates a promise used to deflect un-whitelisted tauri API calls
* Its only purpose is to maintain thenable structure in client code without
* breaking the application
* * @type {Promise<any>}
* @private
*/
var __reject = function () {
return new Promise(function (_, reject) {
reject();
});
}
window.tauri = {
Dir: Dir,
/**
* @name invoke
* @description Calls a Tauri Core feature, such as setTitle
* @param {Object} args
*/
invoke: function invoke(args) {
window.external.invoke(JSON.stringify(args));
},
/**
* @name listen
* @description Add an event listener to Tauri backend
* @param {String} event
* @param {Function} handler
* @param {Boolean} once
*/
listen: function listen(event, handler) {
var once = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
this.invoke({
cmd: 'listen',
event: event,
handler: window.tauri.transformCallback(handler, once),
once: once
});
},
/**
* @name emit
* @description Emits an evt to the Tauri back end
* @param {String} evt
* @param {Object} payload
*/
emit: function emit(evt, payload) {
this.invoke({
cmd: 'emit',
event: evt,
payload: payload
});
},
/**
* @name transformCallback
* @description Registers a callback with a uid
* @param {Function} callback
* @param {Boolean} once
* @returns {*}
*/
transformCallback: function transformCallback(callback) {
var once = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var identifier = uid();
window[identifier] = function (result) {
if (once) {
delete window[identifier];
}
return callback && callback(result);
};
return identifier;
},
/**
* @name promisified
* @description Turns a request into a chainable promise
* @param {Object} args
* @returns {Promise<any>}
*/
promisified: function promisified(args) {
var _this = this;
return new Promise(function (resolve, reject) {
_this.invoke(_objectSpread({
callback: _this.transformCallback(resolve),
error: _this.transformCallback(reject)
}, args));
});
},
/**
* @name readTextFile
* @description Accesses a non-binary file on the user's filesystem
* and returns the content. Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
readTextFile: function readTextFile(path, options) {
return this.promisified({
cmd: 'readTextFile',
path: path,
options: options
});
},
/**
* @name readBinaryFile
* @description Accesses a binary file on the user's filesystem
* and returns the content. Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
readBinaryFile: function readBinaryFile(path, options) {
return this.promisified({
cmd: 'readBinaryFile',
path: path,
options: options
});
},
/**
* @name writeFile
* @description Write a file to the Local Filesystem.
* Permissions based on the app's PID owner
* @param {Object} cfg
* @param {String} cfg.file
* @param {String|Binary} cfg.contents
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
*/
writeFile: function writeFile(cfg, options) {
if (_typeof(cfg) === 'object') {
Object.freeze(cfg);
}
return this.promisified({
cmd: 'writeFile',
file: cfg.file,
contents: cfg.contents,
options: options
});
},
/**
* @name readDir
* @description Reads a directory
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {Boolean} [options.recursive]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
readDir: function readDir(path, options) {
return this.promisified({
cmd: 'readDir',
path: path,
options: options
});
},
/**
* @name createDir
* @description Creates a directory
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {Boolean} [options.recursive]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
createDir: function createDir(path, options) {
return this.promisified({
cmd: 'createDir',
path: path,
options: options
});
},
/**
* @name removeDir
* @description Removes a directory
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {Boolean} [options.recursive]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
removeDir: function removeDir(path, options) {
return this.promisified({
cmd: 'removeDir',
path: path,
options: options
});
},
/**
* @name copyFile
* @description Copy file
* Permissions based on the app's PID owner
* @param {String} source
* @param {String} destination
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
copyFile: function copyFile(source, destination, options) {
return this.promisified({
cmd: 'copyFile',
source: source,
destination: destination,
options: options
});
},
/**
* @name removeFile
* @description Removes a file
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
removeFile: function removeFile(path, options) {
return this.promisified({
cmd: 'removeFile',
path: path,
options: options
});
},
/**
* @name renameFile
* @description Renames a file
* Permissions based on the app's PID owner
* @param {String} path
* @param {Object} [options]
* @param {BaseDirectory} [options.dir]
* @returns {*|Promise<any>|Promise}
*/
renameFile: function renameFile(oldPath, newPath, options) {
return this.promisified({
cmd: 'renameFile',
old_path: oldPath,
new_path: newPath,
options: options
});
},
/**
* @name setTitle
* @description Set the application's title
* @param {String} title
*/
setTitle: function setTitle(title) {
this.invoke({
cmd: 'setTitle',
title: title
});
},
/**
* @name open
* @description Open an URI
* @param {String} uri
*/
open: function open(uri) {
this.invoke({
cmd: 'open',
uri: uri
});
},
/**
* @name execute
* @description Execute a program with arguments.
* Permissions based on the app's PID owner
* @param {String} command
* @param {String|Array} args
* @returns {*|Promise<any>|Promise}
*/
execute: function execute(command, args) {
if (_typeof(args) === 'object') {
Object.freeze(args);
}
return this.promisified({
cmd: 'execute',
command: command,
args: typeof args === 'string' ? [args] : args
});
},
/**
* @name openDialog
* @description Open a file/directory selection dialog
* @param {String} [options]
* @param {String} [options.filter]
* @param {String} [options.defaultPath]
* @param {Boolean} [options.multiple=false]
* @param {Boolean} [options.directory=false]
* @returns {Promise<String|String[]>} promise resolving to the select path(s)
*/
openDialog: function openDialog(options) {
var opts = options || {}
if (_typeof(options) === 'object') {
opts.default_path = opts.defaultPath
Object.freeze(options);
}
return this.promisified({
cmd: 'openDialog',
options: opts
});
},
/**
* @name saveDialog
* @description Open a file/directory save dialog
* @param {String} [options]
* @param {String} [options.filter]
* @param {String} [options.defaultPath]
* @returns {Promise<String>} promise resolving to the select path
*/
saveDialog: function saveDialog(options) {
var opts = options || {}
if (_typeof(options) === 'object') {
opts.default_path = opts.defaultPath
Object.freeze(options);
}
return this.promisified({
cmd: 'saveDialog',
options: opts
});
},
loadAsset: function loadAsset(assetName, assetType) {
return this.promisified({
cmd: 'loadAsset',
asset: assetName,
asset_type: assetType || 'unknown'
})
}
};
// init tauri API
try {
window.tauri.invoke({
cmd: 'init'
})
} catch (e) {
window.addEventListener('DOMContentLoaded', function () {
window.tauri.invoke({
cmd: 'init'
})
}, true)
}
document.addEventListener('error', function (e) {
var target = e.target
while (target != null) {
if (target.matches ? target.matches('img') : target.msMatchesSelector('img')) {
window.tauri.loadAsset(target.src, 'image')
.then(function (img) {
target.src = img
})
break
}
target = target.parentElement
}
}, true)
// open <a href="..."> links with the Tauri API
function __openLinks() {
document.querySelector('body').addEventListener('click', function (e) {
var target = e.target
while (target != null) {
if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
if (target.href && target.href.startsWith('http') && target.target === '_blank') {
window.tauri.open(target.href)
e.preventDefault()
}
break
}
target = target.parentElement
}
}, true)
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
__openLinks()
} else {
window.addEventListener('DOMContentLoaded', function () {
__openLinks()
}, true)
}
</script> <div> <button id="log">Call Log API</button> <button id="request">Call Request (async) API</button> <button id="event">Send event to Rust</button> </div> <div style="margin-top:24px"> <select id="dir"> <option value="">None</option> </select> <input id="path-to-read" placeholder="Type the path to read..."> <button id="read">Read</button> </div> <div style="margin-top:24px"> <input id="url" value="https://tauri.studio"> <button id="open-url">Open URL</button> </div> <div style="margin-top:24px"> <input id="title" value="Awesome Tauri Example!"> <button id="set-title">Set title</button> </div> <div style="margin-top:24px"> <input id="dialog-default-path" placeholder="Default path"> <input id="dialog-filter" placeholder="Extensions filter"> <div> <input type="checkbox" id="dialog-multiple"> <label>Multiple</label> </div> <div> <input type="checkbox" id="dialog-directory"> <label>Directory</label> </div> <button id="open-dialog">Open dialog</button> <button id="save-dialog">Open save dialog</button> </div> <div id="response"></div> <script>function registerResponse(e){document.getElementById("response").innerHTML="object"==typeof e?JSON.stringify(e):e}function addClickEnterHandler(e,n,t){e.addEventListener("click",t),n.addEventListener("keyup",function(e){13===e.keyCode&&t()})}window.onTauriInit=function(){window.tauri.listen("rust-event",function(e){document.getElementById("response").innerHTML=JSON.stringify(e)});var e=document.getElementById("dir");for(var n in window.tauri.Dir){var t=window.tauri.Dir[n],i=document.createElement("option");i.value=t,i.innerHTML=n,e.appendChild(i)}};</script> <script>document.getElementById("log").addEventListener("click",function(){window.tauri.invoke({cmd:"logOperation",event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}),document.getElementById("request").addEventListener("click",function(){window.tauri.promisified({cmd:"performRequest",endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(registerResponse).catch(registerResponse)}),document.getElementById("event").addEventListener("click",function(){window.tauri.emit("js-event","this is the payload string")});</script> <script>var dirSelect=document.getElementById("dir");function getDir(){return dirSelect.value?parseInt(dir.value):null}function arrayBufferToBase64(e,n){var t=new Blob([e],{type:"application/octet-binary"}),r=new FileReader;r.onload=function(e){var t=e.target.result;n(t.substr(t.indexOf(",")+1))},r.readAsDataURL(t)}var pathInput=document.getElementById("path-to-read");addClickEnterHandler(document.getElementById("read"),pathInput,function(){var r=pathInput.value,a=r.match(/\S+\.\S+$/g),e={dir:getDir()};(a?window.tauri.readBinaryFile(r,e):window.tauri.readDir(r,e)).then(function(e){if(a)if(r.includes(".png")||r.includes(".jpg"))arrayBufferToBase64(new Uint8Array(e),function(e){registerResponse('<img src="'+("data:image/png;base64,"+e)+'"></img>')});else{var t=String.fromCharCode.apply(null,e);registerResponse('<textarea id="file-response" style="height: 400px"></textarea><button id="file-save">Save</button>');var n=document.getElementById("file-response");n.value=t,document.getElementById("file-save").addEventListener("click",function(){window.tauri.writeFile({file:r,contents:n.value},{dir:getDir()}).catch(registerResponse)})}else registerResponse(e)}).catch(registerResponse)});</script> <script>var urlInput=document.getElementById("url");addClickEnterHandler(document.getElementById("open-url"),urlInput,function(){window.tauri.open(urlInput.value)});var titleInput=document.getElementById("title");addClickEnterHandler(document.getElementById("set-title"),titleInput,function(){window.tauri.setTitle(titleInput.value)});</script> <script>var defaultPathInput=document.getElementById("dialog-default-path"),filterInput=document.getElementById("dialog-filter"),multipleInput=document.getElementById("dialog-multiple"),directoryInput=document.getElementById("dialog-directory");document.getElementById("open-dialog").addEventListener("click",function(){window.tauri.openDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null,multiple:multipleInput.checked,directory:directoryInput.checked}).then(registerResponse).catch(registerResponse)}),document.getElementById("save-dialog").addEventListener("click",function(){window.tauri.saveDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null}).then(registerResponse).catch(registerResponse)});</script> </body></html>

View File

@ -0,0 +1,19 @@
var urlInput = document.getElementById('url')
addClickEnterHandler(
document.getElementById('open-url'),
urlInput,
function () {
window.tauri.open(urlInput.value)
}
)
var titleInput = document.getElementById('title')
addClickEnterHandler(
document.getElementById('set-title'),
titleInput,
function () {
window.tauri.setTitle(titleInput.value)
}
)

View File

@ -0,0 +1,10 @@
{
"name": "communication-example",
"version": "1.0.0",
"description": "A Tauri example showcasing the JS-Rust communication",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"private": true
}

View File

@ -0,0 +1,11 @@
# Generated by Cargo
# will have compiled files and executables
/target/
WixTools
# These are backup files generated by rustfmt
**/*.rs.bk
tauri.js
config.json
bundle.json

View File

@ -0,0 +1,39 @@
workspace = { }
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = [ "you" ]
license = ""
repository = ""
default-run = "app"
edition = "2018"
build = "src/build.rs"
[package.metadata.bundle]
identifier = "com.tauri.dev"
icon = [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
tauri = { path = "../../..", features = [ "all-api", "edge" ] }
[target."cfg(windows)".build-dependencies]
winres = "0.1"
[features]
dev-server = [ "tauri/dev-server" ]
embedded-server = [ "tauri/embedded-server" ]
no-server = [ "tauri/no-server" ]
[[bin]]
name = "app"
path = "src/main.rs"

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More