Compare commits

...

13 commits
v0.1.1 ... main

Author SHA1 Message Date
rzmk
1feca4d720
build: update to magika 0.2.8 2024-03-06 11:53:10 -05:00
rzmk
ca183c07aa
build: update dependencies & format code 2024-03-04 21:03:10 -05:00
rzmk
4b2bd9ddae
chore: release v0.1.2 2024-02-26 22:46:46 -05:00
rzmk
80f32172a7
build: update dependencies 2024-02-26 22:44:49 -05:00
rzmk
7f99660309
ui: add tooltips & clear table button 2024-02-26 21:51:11 -05:00
rzmk
199abf5553
build: use 'release' instead for release-it shorthand due to habit 2024-02-21 19:08:00 -05:00
rzmk
95d7aecfbe
build: add 'rel' shorthand for release-it 2024-02-21 18:53:31 -05:00
rzmk
287767fa19
build: remove blank screen on installation 2024-02-21 18:52:43 -05:00
rzmk
0f8ce56de1
docs: fix typo & clarify disclaimer 2024-02-21 16:04:42 -05:00
rzmk
a30968fe7d
docs: clarify using tauri v2 beta 2024-02-21 13:01:54 -05:00
rzmk
688f8e9e5d
build: update tauri dependencies 2024-02-21 12:41:49 -05:00
rzmk
274b1a7e20
ci: update environment variable names & .gitignore 2024-02-20 13:27:52 -05:00
rzmk
5db40d07a5
feat: add updater 2024-02-20 13:09:51 -05:00
15 changed files with 1039 additions and 1079 deletions

View file

@ -5,9 +5,9 @@ on:
tags:
- "*"
workflow_dispatch:
# This is the example from the readme.
# On each push to the `release` branch it will create or update a GitHub release, build your app, and upload the artifacts to the release.
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
jobs:
publish-tauri:
@ -17,8 +17,6 @@ jobs:
fail-fast: false
matrix:
settings:
# - platform: "macos-latest"
# args: "--target universal-apple-darwin"
- platform: "macos-latest"
args: "--target x86_64-apple-darwin"
- platform: "macos-latest"
@ -59,7 +57,7 @@ jobs:
with:
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version
releaseName: "v__VERSION__"
releaseBody: "See the assets to download this version of fformat and install."
releaseBody: "Learn about this release at https://github.com/rzmk/fformat/releases"
releaseDraft: true
prerelease: false
args: ${{ matrix.settings.args }}

View file

@ -20,7 +20,7 @@ A desktop app integrated with Google's deep learning model [Magika](https://gith
- **Powered by a deep learning model from Google's Magika team**
- **Run offline locally:** Once you've installed fformat, you may run it without an internet connection.
- **Interactive data table**: After Magika detects potential file content types for all of your files, you may view more info about the results.
- **Organized metadata:** Learn about your file's path, potential file content types, and score representing a probability that the file content types is as expected.
- **Organized metadata:** Learn about your file's path, potential file content types, and score representing a probability that the file content type is as expected.
- **Drag & drop files:** Choose between dropping one or more files simply dragging and dropping them into fformat.
- **Light & dark themes:** Toggle your theme by clicking on the sun and moon icon.
- **Low file size:** fformat is only a few megabytes.
@ -28,7 +28,7 @@ A desktop app integrated with Google's deep learning model [Magika](https://gith
## 📚 Tech Stack
- [Magika](https://github.com/google/magika) - Deep learning model (using JavaScript browser API) from Google
- [Tauri v2](https://beta.tauri.app) - Desktop/Mobile app framework
- [Tauri v2 Beta](https://beta.tauri.app) - Desktop/Mobile app framework
- [Next.js](https://nextjs.org/) - Web framework built with React
- [TypeScript](https://www.typescriptlang.org/) - Programming language
- [Rust](https://www.rust-lang.org/) - Programming language
@ -44,19 +44,19 @@ Download the relevant installer for your system from the [releases page](https:/
## 🤝 Contributing
Contributions are welcome! If you have any ideas, fixes, or suggestions, please open an [issue](https://github.com/rzmk/fformat/issues) or submit a [pull request](https://github.com/rzmk/fformat/pulls).
Contributions are welcome! If you have any ideas, fixes, or suggestions, please open an [issue](https://github.com/rzmk/fformat/issues) or submit a [pull request](https://github.com/rzmk/fformat/pulls). Issues and pull requests may or may not be completed/merged.
Some documentation that may be useful include:
- [Tauri v2 docs](https://beta.tauri.app)
- [Tauri v2 Beta docs](https://beta.tauri.app)
- [shadcn/ui docs](https://ui.shadcn.com/docs)
- [Magika repo](https://github.com/google/magika)
## Disclaimer
This project is not affiliated with Google.
fformat is not affiliated with Google, nor is fformat endorsed by Google.
By using this project you acknowledge the following:
By using fformat you accept and acknowledge the following:
- fformat may display content (e.g., from Magika, links) that does not reflect the views of the project owner.
- fformat may display inaccurate information that seem factual but is not and other false/inaccurate information (e.g., inaccurate file content types from Magika's detection).
- fformat may display content (e.g., from Magika, external links) that does not reflect the views of the author of fformat.
- fformat may display inaccurate information that seem factual but is not such as inaccurate file content types from Magika's output (therefore denoting fformat as detecting "potential" file content types).

View file

@ -1,48 +1,50 @@
{
"name": "fformat",
"version": "0.1.1",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@tanstack/react-table": "^8.12.0",
"@tauri-apps/api": "2.0.0-beta.1",
"@tauri-apps/plugin-dialog": "2.0.0-beta.0",
"@tauri-apps/plugin-fs": "github:tauri-apps/tauri-plugin-fs#v2",
"@tauri-apps/plugin-shell": "github:tauri-apps/tauri-plugin-shell#v2",
"class-variance-authority": "^0.7.0",
"lucide-react": "^0.334.0",
"magika": "^0.2.5",
"next": "14.1.0",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@release-it/bumper": "^6.0.1",
"@release-it/conventional-changelog": "^8.0.1",
"@tauri-apps/cli": "2.0.0-beta.1",
"@types/node": "^20.11.19",
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"autoprefixer": "^10.4.17",
"clsx": "^2.1.0",
"eslint": "^8.56.0",
"eslint-config-next": "14.1.0",
"postcss": "^8.4.35",
"release-it": "^17.1.1",
"tailwind-merge": "^2.2.1",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3"
}
"name": "fformat",
"version": "0.1.2",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"release": "release-it"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-table": "^8.13.2",
"@tauri-apps/api": "2.0.0-beta.1",
"@tauri-apps/plugin-dialog": "2.0.0-beta.0",
"@tauri-apps/plugin-fs": "github:tauri-apps/tauri-plugin-fs#v2",
"@tauri-apps/plugin-shell": "github:tauri-apps/tauri-plugin-shell#v2",
"class-variance-authority": "^0.7.0",
"lucide-react": "^0.334.0",
"magika": "^0.2.8",
"next": "14.1.0",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@release-it/bumper": "^6.0.1",
"@release-it/conventional-changelog": "^8.0.1",
"@tauri-apps/cli": "2.0.0-beta.1",
"@types/node": "^20.11.24",
"@types/react": "^18.2.63",
"@types/react-dom": "^18.2.20",
"autoprefixer": "^10.4.18",
"clsx": "^2.1.0",
"eslint": "^8.57.0",
"eslint-config-next": "14.1.0",
"postcss": "^8.4.35",
"release-it": "^17.1.1",
"tailwind-merge": "^2.2.1",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3"
}
}

916
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -2,3 +2,6 @@
# will have compiled files and executables
/target/
/gen/schemas
# personal utility for cargo deny
deny.toml

859
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,6 @@ name = "fformat"
version = "0.1.0"
description = "Potential file content type identifier."
authors = ["rzmk"]
license = ""
repository = "https://github.com/rzmk/fformat"
edition = "2021"
rust-version = "1.70"
@ -15,15 +14,15 @@ name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.0.0-beta.2", features = [] }
tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2.0.0-beta.3", features = [] }
tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace.git", branch = "chore/tauri-beta-3" }
tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace.git", branch = "chore/tauri-beta-3" }
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace.git", branch = "chore/tauri-beta-3" }
tauri = { version = "2.0.0-beta", features = [] }
tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
[profile.release]
panic = "abort" # Strip expensive panic clean-up logic

View file

@ -1,11 +1,9 @@
use tauri_plugin_shell;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View file

@ -2,5 +2,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
app_lib::run();
app_lib::run();
}

View file

@ -35,9 +35,17 @@
"plugins": {
"shell": {
"open": "^https://github\\.com/rzmk"
},
"updater": {
"active": true,
"endpoints": [
"https://github.com/rzmk/fformat/releases/latest/download/latest.json"
],
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5NkYxRUMxOTBFRkE1REQKUldUZHBlK1F3UjV2R1ZTUStEVXRBVE1NdnN6YUZIWFhJMUZuUzFwWTlCeDRCVzU1d1VsNEVkeWkK"
}
},
"identifier": "com.fformat.dev",
"productName": "fformat",
"version": "0.1.1"
"version": "0.1.2"
}

View file

@ -29,18 +29,26 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useState } from "react";
import { DataTableViewOptions } from "@/components/DT/DataTableViewOptions";
import { LucideScanSearch } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { LucideScanSearch, Trash2 } from "lucide-react";
import { useState } from "react";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
setData: React.Dispatch<React.SetStateAction<TData[]>>;
}
export function DataTable<TData, TValue>({
columns,
data,
setData,
}: DataTableProps<TData, TValue>) {
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [searchColumn, setSearchColumn] = useState("label");
@ -79,10 +87,24 @@ export function DataTable<TData, TValue>({
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<LucideScanSearch strokeWidth={1.25} />
</Button>
<DropdownMenuTrigger>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="icon">
<LucideScanSearch
strokeWidth={1.25}
/>
<span className="sr-only">
Choose a column to search
</span>
</Button>
</TooltipTrigger>
<TooltipContent>
Choose a column to search
</TooltipContent>
</Tooltip>
</TooltipProvider>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>Search Column</DropdownMenuLabel>
@ -107,6 +129,21 @@ export function DataTable<TData, TValue>({
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="destructive"
size="icon"
onClick={() => setData([])}
>
<Trash2 strokeWidth={1.25} />
<span className="sr-only">Clear table</span>
</Button>
</TooltipTrigger>
<TooltipContent>Clear table</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<DataTableViewOptions table={table} />
</div>

View file

@ -3,12 +3,10 @@
import { Button } from "@/components/ui/button";
import { Loader } from "@/components/ui/loader";
//@ts-ignore
import { Magika } from "magika";
import { listen } from "@tauri-apps/api/event";
import { useEffect, useState } from "react";
import { readFile } from "@tauri-apps/plugin-fs";
//@ts-ignore
import { open } from "@tauri-apps/plugin-dialog";
import { columns } from "@/components/DT/columns";
import { DataTable } from "@/components/DT/data-table";
@ -19,22 +17,19 @@ const MagikaProcess = () => {
useEffect(() => {
if (loading) return;
const unlisten = listen(
"tauri://file-drop",
async ({ payload }: any) => {
setLoading(true);
const filepaths: string[] = payload.paths;
const filepredictions: any[] = [];
listen("tauri://file-drop", async ({ payload }: any) => {
setLoading(true);
const filepaths: string[] = payload.paths;
const filepredictions: any[] = [];
for await (const filepath of filepaths) {
const prediction = await getPrediction(filepath);
filepredictions.push(prediction);
}
setPredictions(filepredictions);
setLoading(false);
for await (const filepath of filepaths) {
const prediction = await getPrediction(filepath);
filepredictions.push(prediction);
}
);
setPredictions(filepredictions);
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -43,9 +38,13 @@ const MagikaProcess = () => {
const fileBytes = await readFile(filepath);
const magika = new Magika();
await magika.load({});
const prediction = await magika.identifyBytes(fileBytes);
prediction.path = filepath;
return prediction;
const result = await magika.identifyBytes(
new Uint16Array(fileBytes.buffer)
);
return {
path: filepath,
result: result,
};
} catch (e) {
console.error(
`Error while getting prediction for ${filepath}: ${e}`
@ -68,7 +67,6 @@ const MagikaProcess = () => {
}
setPredictions(filePredictions);
} else if (selected === null) {
// user cancelled the selection
console.log("User cancelled selection");
} else {
// user selected a single file
@ -110,7 +108,11 @@ const MagikaProcess = () => {
</p>
</div>
{predictions.length > 0 && (
<DataTable columns={columns} data={predictions} />
<DataTable
columns={columns}
data={predictions}
setData={setPredictions}
/>
)}
</div>
</div>

View file

@ -11,23 +11,38 @@ import {
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { open } from "@tauri-apps/plugin-shell";
const Settings = () => {
return (
<Dialog>
<DialogTrigger asChild>
<DialogTrigger>
{/* <SettingsIcon strokeWidth={1.25} /> */}
<Button variant="ghost" size="icon">
<Info strokeWidth={1.25} />
</Button>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
<Info strokeWidth={1.25} />
<span className="sr-only">About fformat</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">
About fformat
</TooltipContent>
</Tooltip>
</TooltipProvider>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>fformat</DialogTitle>
<p className="text-sm text-muted-foreground">
Identify potential file content types on your local
device.
Identify potential file content types on your device.
</p>
<Separator />
<ul className="list-disc text-sm text-muted-foreground ml-4 mt-2">

View file

@ -3,20 +3,35 @@
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
const ThemeSwitch = () => {
const { theme, setTheme } = useTheme();
return (
<Button
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
variant="ghost"
size="icon"
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={() =>
setTheme(theme === "light" ? "dark" : "light")
}
variant="ghost"
size="icon"
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">Toggle theme</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};

View file

@ -0,0 +1,30 @@
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
const TooltipProvider = TooltipPrimitive.Provider
const Tooltip = TooltipPrimitive.Root
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }