diff --git a/.gitignore b/.gitignore
index a547bf3..fd39852 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+
+# Tauri
+/artifacts
+latest.json
\ No newline at end of file
diff --git a/README.md b/README.md
index fa033bc..9027fac 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@
## ⬇️ Download
-- macOS: dmg
+- macOS: dmg
- Windows: exe | microsoft store - Coming soon...
- Linux: deb | flathub - Coming soon...
@@ -61,10 +61,9 @@ If you want to report a bug, first, thank you, that helps us a lot. Please open
### v1.1.x
- Add ability to reorder colors
+- Add ability to reorder palettes
- More color formats:
- Common:
- - Oklab `oklch(40.1% 0.123 21.57)`
- - HSB/HSV `268, 69, 57`
- RGB/RGBA from 0 to 1 `0,36; 0,18; 0,57`
- LAB
- RAL
@@ -81,7 +80,8 @@ If you want to report a bug, first, thank you, that helps us a lot. Please open
- 🖥️ Obj-C NSColor Calibrated RGB
- 📱 Obj-C UIColor RGB
- 🌊 .NET RGB/ARGB
- - ☕ Java RGB/RGBA
+ - ☕ Java HEX
+ - ☕ Java RGB
- 📱 Android RGB/ARGB
- Custom color formatter
- Add ability to change global shortcuts
@@ -91,10 +91,12 @@ If you want to report a bug, first, thank you, that helps us a lot. Please open
- Control Select with arrows up/down
- Search for colors in palette
- Fix: Sometimes the cursor is not visible - Hide cursor with `set_cursor_visible` ([issue](https://github.com/tauri-apps/tauri/issues/10231))
-- Add app updater
- [Aperture size](https://github.com/ModuleArt/plain-color/pull/9#issuecomment-2599870209)
-- Improve list performance (infinite scroll)
- Move picker with arrows (step = 1px), make sure it cannot be out of screen bounds
- Settings: Add "Reset to defaults" button
- Instant copy shortcut `CommandOrControl+Alt+C`
- Instant pick shortcut `CommandOrControl+Shift+C`
+- Improve list performance (infinite scroll)
+- Add color formats:
+ - Oklab `oklch(40.1% 0.123 21.57)`
+ - HSB/HSV `268, 69, 57`
diff --git a/docs/RELEASE.md b/docs/RELEASE.md
new file mode 100644
index 0000000..b7f746c
--- /dev/null
+++ b/docs/RELEASE.md
@@ -0,0 +1,32 @@
+# How to release a new version
+
+### Preparation (skip if it's already done)
+
+1. Place `tauri.key` and `tauri.key.pub` files into `~/.tauri` folder
+2. Add this to your `.zshrc`:
+
+```
+export TAURI_SIGNING_PRIVATE_KEY="$HOME/.tauri/tauri.key"
+export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$$$"
+```
+
+3. Refresh `.zshrc`:
+
+```bash
+source ~/.zshrc
+```
+
+where `$$$` is password for the `tauri.key`
+
+### Build
+
+1. Build bundle:
+
+```bash
+yarn t:build:release
+```
+
+### Release
+
+1. Create a new GitHub release
+2. Attach all files from `/artifacts` folder to the release
diff --git a/package.json b/package.json
index 531e445..27ef60c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,9 @@
{
"name": "plain-color",
+ "description": "Lightweight, versatile, cross-platform color picker app",
"private": false,
- "version": "1.0.9",
+ "version": "1.0.10",
+ "license": "GPL-3.0-only",
"type": "module",
"scripts": {
"dev": "vite",
@@ -9,7 +11,7 @@
"preview": "vite preview",
"t:dev": "tauri dev",
"t:build:debug": "tauri build --debug",
- "t:build:release": "tauri build"
+ "t:build:release": "tauri build && node src-tauri/updater/generate-artifacts.js"
},
"dependencies": {
"@phosphor-icons/react": "^2.1.7",
@@ -23,6 +25,7 @@
"@tauri-apps/plugin-os": "~2",
"@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-shell": "^2",
+ "@tauri-apps/plugin-updater": "~2",
"@tauri-apps/plugin-window": "^2.0.0-alpha.1",
"classnames": "^2.5.1",
"color-is-dark": "^1.0.1",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 5362d08..a06970d 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -4,7 +4,7 @@ version = 3
[[package]]
name = "PlainColor"
-version = "1.0.9"
+version = "1.0.10"
dependencies = [
"base64 0.22.1",
"cocoa",
@@ -27,6 +27,7 @@ dependencies = [
"tauri-plugin-process",
"tauri-plugin-shell",
"tauri-plugin-single-instance",
+ "tauri-plugin-updater",
"termcolor",
"tokio",
]
@@ -100,6 +101,15 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "arbitrary"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
+dependencies = [
+ "derive_arbitrary",
+]
+
[[package]]
name = "arboard"
version = "3.4.1"
@@ -960,6 +970,17 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
[[package]]
name = "derive_more"
version = "0.99.18"
@@ -1253,6 +1274,18 @@ dependencies = [
"rustc_version 0.4.1",
]
+[[package]]
+name = "filetime"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "libredox",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "flate2"
version = "1.0.34"
@@ -2358,6 +2391,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.6.0",
"libc",
+ "redox_syscall",
]
[[package]]
@@ -2450,6 +2484,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+[[package]]
+name = "minisign-verify"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6367d84fb54d4242af283086402907277715b8fe46976963af5ebf173f8efba3"
+
[[package]]
name = "miniz_oxide"
version = "0.8.0"
@@ -2589,7 +2629,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [
- "proc-macro-crate 1.3.1",
+ "proc-macro-crate 3.2.0",
"proc-macro2",
"quote",
"syn 2.0.87",
@@ -4468,6 +4508,17 @@ dependencies = [
"syn 2.0.87",
]
+[[package]]
+name = "tar"
+version = "0.4.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
[[package]]
name = "target-lexicon"
version = "0.12.16"
@@ -4797,6 +4848,36 @@ dependencies = [
"zbus 5.3.0",
]
+[[package]]
+name = "tauri-plugin-updater"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce2d39224390c41ba544f02b4f1721f42256320b3fb8c371e9425cbddeb4a68c"
+dependencies = [
+ "base64 0.22.1",
+ "dirs",
+ "flate2",
+ "futures-util",
+ "http",
+ "infer",
+ "minisign-verify",
+ "percent-encoding",
+ "reqwest",
+ "semver 1.0.23",
+ "serde",
+ "serde_json",
+ "tar",
+ "tauri",
+ "tauri-plugin",
+ "tempfile",
+ "thiserror 2.0.9",
+ "time 0.3.36",
+ "tokio",
+ "url",
+ "windows-sys 0.59.0",
+ "zip",
+]
+
[[package]]
name = "tauri-runtime"
version = "2.2.0"
@@ -6220,6 +6301,17 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
+[[package]]
+name = "xattr"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909"
+dependencies = [
+ "libc",
+ "linux-raw-sys",
+ "rustix",
+]
+
[[package]]
name = "xdg-home"
version = "1.3.0"
@@ -6446,6 +6538,21 @@ dependencies = [
"syn 2.0.87",
]
+[[package]]
+name = "zip"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45"
+dependencies = [
+ "arbitrary",
+ "crc32fast",
+ "crossbeam-utils",
+ "displaydoc",
+ "indexmap 2.6.0",
+ "memchr",
+ "thiserror 2.0.9",
+]
+
[[package]]
name = "zvariant"
version = "4.0.0"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index dcb17eb..baa5868 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "PlainColor"
-version = "1.0.9"
-description = "A Tauri App"
+version = "1.0.10"
+description = "Lightweight, versatile, cross-platform color picker app"
authors = ["Eugene Volynko "]
edition = "2021"
@@ -44,3 +44,4 @@ cocoa = "0.26.0"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-global-shortcut = "2"
tauri-plugin-single-instance = "2"
+tauri-plugin-updater = "2"
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index 1515644..c6f3d2f 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -50,6 +50,9 @@
]
},
"deep-link:default",
- "deep-link:allow-get-current"
+ "deep-link:allow-get-current",
+ "updater:default",
+ "updater:allow-check",
+ "updater:allow-download-and-install"
]
}
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index e0dc61b..873f034 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -11,7 +11,7 @@ mod mod_screenshot;
use tauri::{
generate_context, generate_handler,
menu::{Menu, PredefinedMenuItem, Submenu},
- Builder, DragDropEvent, Emitter, Manager, WindowEvent,
+ DragDropEvent, Emitter, Manager, WindowEvent,
};
#[allow(non_upper_case_globals)]
@@ -24,7 +24,7 @@ pub fn run() {
let loop_state =
std::sync::Arc::new(tokio::sync::Mutex::new(mod_pickerloop::LoopState::default()));
- Builder::default()
+ tauri::Builder::default()
.manage(loop_state)
.menu(|handle| {
Menu::with_items(
@@ -45,6 +45,7 @@ pub fn run() {
],
)
})
+ .plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 86e8228..8ee73c4 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "PlainColor",
- "version": "1.0.9",
+ "version": "1.0.10",
"identifier": "com.moduleart.plaincolor",
"build": {
"beforeDevCommand": "yarn dev",
@@ -84,6 +84,16 @@
"role": "Editor",
"mimeType": "application/octet-stream"
}
- ]
+ ],
+ "createUpdaterArtifacts": true
+ },
+ "plugins": {
+ "updater": {
+ "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEJBQkJEQkYwMjQ2ODk5NTYKUldSV21XZ2s4TnU3dXB2andtOS9vK0o1UE91MjBaZHR6T2g5UEpScHNTK0FBMExmRURLdjlUZ3oK",
+ "endpoints": ["https://github.com/ModuleArt/plain-color/releases/latest/download/latest.json"],
+ "windows": {
+ "installMode": "passive"
+ }
+ }
}
}
diff --git a/src-tauri/updater/generate-artifacts.js b/src-tauri/updater/generate-artifacts.js
new file mode 100644
index 0000000..c394b54
--- /dev/null
+++ b/src-tauri/updater/generate-artifacts.js
@@ -0,0 +1,79 @@
+import fs from 'fs'
+import path from 'path'
+
+// load version
+
+const packageJsonPath = path.join('package.json')
+let packageJson = {}
+try {
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
+} catch (error) {
+ console.error('Failed to read package.json:', error)
+ process.exit(1)
+}
+
+const VERSION = packageJson.version
+if (!VERSION) {
+ console.error('Version is missing in package.json!')
+ process.exit(1)
+}
+
+// load signature
+
+const signaturePath = path.join('src-tauri', 'target', 'release', 'bundle', 'macos', 'PlainColor.app.tar.gz.sig')
+let signature = ''
+try {
+ signature = fs.readFileSync(signaturePath, 'utf-8').trim()
+} catch (error) {
+ console.error('Failed to read signature file:', error)
+ process.exit(1)
+}
+
+const DOWNLOAD_URL = 'https://github.com/user/repo/releases/latest/download/PlainColor.app.tar.gz'
+
+const latestJson = {
+ version: VERSION,
+ notes: 'Update',
+ pub_date: new Date().toISOString(),
+ platforms: {
+ aarch64: {
+ url: DOWNLOAD_URL,
+ signature,
+ },
+ },
+}
+
+// write artifacts
+
+const artifactsPath = path.join('artifacts')
+const latestJsonOutputPath = path.join(artifactsPath, 'latest.json')
+
+try {
+ if (!fs.existsSync(artifactsPath)) {
+ fs.mkdirSync(artifactsPath)
+ }
+} catch (error) {
+ console.error('Cannot access artifacts directory:', error)
+ process.exit(1)
+}
+
+try {
+ fs.writeFileSync(latestJsonOutputPath, JSON.stringify(latestJson, null, 2), 'utf-8')
+ console.log(`latest.json generated successfully at ${latestJsonOutputPath}`)
+} catch (error) {
+ console.error('Failed to generate latest.json:', error)
+ process.exit(1)
+}
+
+const updaterName = 'PlainColor.app.tar.gz'
+const updaterPath = path.join('src-tauri', 'target', 'release', 'bundle', 'macos', updaterName)
+const dmgName = `PlainColor_${VERSION}_aarch64.dmg`
+const dmgPath = path.join('src-tauri', 'target', 'release', 'bundle', 'dmg', dmgName)
+
+try {
+ fs.copyFileSync(updaterPath, path.join(artifactsPath, updaterName))
+ fs.copyFileSync(dmgPath, path.join(artifactsPath, dmgName))
+} catch (error) {
+ console.error('Failed to copy artifacts:', error)
+ process.exit(1)
+}
diff --git a/src/components/Updater/index.tsx b/src/components/Updater/index.tsx
new file mode 100644
index 0000000..aa37ef2
--- /dev/null
+++ b/src/components/Updater/index.tsx
@@ -0,0 +1,70 @@
+import { FC, useEffect, useState } from 'react'
+import { check, Update } from '@tauri-apps/plugin-updater'
+import { relaunch } from '@tauri-apps/plugin-process'
+import { Text } from '@/components/Text'
+import { Stack } from '@/components/Stack'
+import { Button } from '@/components/Button'
+
+export const Updater: FC = () => {
+ const [updateAvailable, setUpdateAvailable] = useState(null)
+ const [isDownloading, setIsDownloading] = useState(false)
+ const [contentLength, setContentLength] = useState(0)
+ const [downloadedLength, setDownloadedLength] = useState(0)
+ const [isFinished, setIsFinished] = useState(false)
+
+ useEffect(() => {
+ check().then((update) => {
+ if (update) {
+ setUpdateAvailable(update)
+ }
+ })
+ }, [])
+
+ const downloadAndInstall = () => {
+ setIsDownloading(true)
+
+ updateAvailable
+ ?.downloadAndInstall((event) => {
+ switch (event.event) {
+ case 'Started':
+ setContentLength(event.data.contentLength || 0)
+ break
+ case 'Progress':
+ setDownloadedLength(downloadedLength + event.data.chunkLength)
+ break
+ case 'Finished':
+ setIsFinished(true)
+ break
+ }
+ })
+ .then(() => {
+ relaunch()
+ })
+ }
+
+ if (!updateAvailable) return null
+
+ return (
+
+
+
+
+
+ {!isDownloading && (
+
+
+
+ )}
+
+ )
+}
diff --git a/src/layouts/AppLayout/index.tsx b/src/layouts/AppLayout/index.tsx
index f650f41..625c768 100644
--- a/src/layouts/AppLayout/index.tsx
+++ b/src/layouts/AppLayout/index.tsx
@@ -38,8 +38,10 @@ export const AppLayout: FC = () => {
const navigate = useNavigate()
const openWithHandler = (palettes: IPalette[]) => {
- palettes.map((palette) => palettesStore.addPalette(palette))
- navigate('/palettes')
+ if (palettes.length) {
+ palettes.map((palette) => palettesStore.addPalette(palette))
+ navigate('/palettes')
+ }
}
useEffect(() => {
diff --git a/src/pages/SettingsPage/index.tsx b/src/pages/SettingsPage/index.tsx
index 7c7ff22..c71d3e8 100644
--- a/src/pages/SettingsPage/index.tsx
+++ b/src/pages/SettingsPage/index.tsx
@@ -16,6 +16,7 @@ import {
} from '@/utils/cmd/macosPermissions.cmd.util'
import { invokeSetPickerColorProfile } from '@/utils/cmd/picker.cmd.util'
import { Scroller } from '@/components/Scroller'
+import { Updater } from '@/components/Updater'
export const SettingsPage: FC = () => {
const navigate = useNavigate()
@@ -71,6 +72,7 @@ export const SettingsPage: FC = () => {
+
diff --git a/yarn.lock b/yarn.lock
index a109544..ecfaafd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -632,6 +632,13 @@
dependencies:
"@tauri-apps/api" "^2.0.0"
+"@tauri-apps/plugin-updater@~2":
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-updater/-/plugin-updater-2.3.1.tgz#203a408ed4c47ebfbf8062fe55940d4d4aba890b"
+ integrity sha512-D1MOWDO1Pqy33gloq0iifny+vwHxMyTshzF0q0bJRwibjL5SXd1xSQwBX24TRug7K0QIu8yOnveQHzI3GcdmfA==
+ dependencies:
+ "@tauri-apps/api" "^2.0.0"
+
"@tauri-apps/plugin-window@^2.0.0-alpha.1":
version "2.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-window/-/plugin-window-2.0.0-alpha.1.tgz#28a0217100fc5a34fb2a6d76103ba056b2348286"