add firebase

main
reng 4 months ago
parent db8407c1c3
commit 561564a034
  1. 1063
      package-lock.json
  2. 1
      package.json
  3. 4
      src-tauri/2
  4. 19
      src-tauri/capabilities/default.json
  5. 107
      src-tauri/src/lib.rs
  6. 4
      src-tauri/src/raw_convert.rs
  7. 4
      src-tauri/src/util.rs
  8. 9
      src-tauri/tauri.conf.json
  9. 13
      src/App.css
  10. 144
      src/App.jsx
  11. 77
      src/utils/backend.js
  12. 29
      src/utils/fs.js

1063
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -15,6 +15,7 @@
"@tauri-apps/plugin-dialog": "^2.3.2", "@tauri-apps/plugin-dialog": "^2.3.2",
"@tauri-apps/plugin-fs": "^2.4.1", "@tauri-apps/plugin-fs": "^2.4.1",
"@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-opener": "^2",
"firebase": "^12.0.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1"
}, },

@ -1,7 +1,7 @@
added 1 package, and audited 170 packages in 4s up to date, audited 256 packages in 4s
25 packages are looking for funding 28 packages are looking for funding
run `npm fund` for details run `npm fund` for details
found 0 vulnerabilities found 0 vulnerabilities

@ -9,6 +9,23 @@
"core:default", "core:default",
"opener:default", "opener:default",
"dialog:default", "dialog:default",
"fs:default" "fs:write-files",
"fs:allow-create",
"fs:allow-appdata-write",
"fs:allow-appdata-read",
"fs:allow-exists",
{
"identifier": "fs:scope",
"allow": [
{"path":"$DOCUMENT/**/*"},
{"path":"$DOCUMENT"},
{
"path": "$APPDATA"
},
{
"path": "$APPDATA/**/*"
}
]
}
] ]
} }

@ -66,7 +66,7 @@ fn get_printer_capabilities(device_name: String) -> HashMap<String, Vec<String>>
// println!("Printer Capabilities XML: {}", string_xml); // println!("Printer Capabilities XML: {}", string_xml);
let quality_opts = util::get_output_quality_options(&string_xml); let quality_opts = util::get_output_quality_options(&string_xml);
println!("DPI Options: {:?}", quality_opts); println!("DPI Options: {:?}", quality_opts);
map.insert("quality_options".to_string(), quality_opts); map.insert("quality_options".to_string(), quality_opts);
} }
@ -131,14 +131,18 @@ fn default_ticket(quality: Option<String>) -> PrintTicket {
fn print_document( fn print_document(
device_name: String, device_name: String,
document_path: String, document_path: String,
dpi: String, dpi: Option<String>,
color: String, color: Option<String>,
size: String, size: Option<String>,
quality: Option<String>, quality: Option<String>,
) { ) {
println!( println!(
"Printing document: {}, {}, {}, {}, {}", "Printing document: {}, size: {}, dpi: {}, color: {}, quality: {}",
document_path, size, dpi, color, quality.clone().unwrap_or_else(|| "none".to_string()) document_path,
size.clone().unwrap_or_else(|| "none".to_string()),
dpi.clone().unwrap_or_else(|| "none".to_string()),
color.clone().unwrap_or_else(|| "none".to_string()),
quality.clone().unwrap_or_else(|| "none".to_string())
); );
let path = Path::new(&document_path); let path = Path::new(&document_path);
@ -178,58 +182,67 @@ fn print_document(
let capabilities = PrintCapabilities::fetch(&my_device).unwrap(); let capabilities = PrintCapabilities::fetch(&my_device).unwrap();
// size let mut builder = PrintTicketBuilder::new(&my_device).unwrap();
let parsed_size = size
.parse::<PredefinedMediaName>()
.expect("Invalid media size name");
let a6_media = capabilities
.page_media_sizes()
.find(|x| x.as_predefined_name() == Some(parsed_size))
.expect("Media size not supported by printer");
// size
if let Some(size) = &size {
let parsed_size = size
.parse::<PredefinedMediaName>()
.expect("Invalid media size name");
let media = capabilities
.page_media_sizes()
.find(|x| x.as_predefined_name() == Some(parsed_size))
.expect("Media size not supported by printer");
builder.merge(media).unwrap();
}
// dpi // dpi
let parsed_dpi = dpi if let Some(dpi) = &dpi {
.split('x') let parsed_dpi = dpi
.next() .split('x')
.and_then(|v| v.parse::<u32>().ok()) .next()
.expect("Invalid DPI format"); .and_then(|v| v.parse::<u32>().ok())
let supported_dpi = capabilities .expect("Invalid DPI format");
.page_resolutions() let supported_dpi = capabilities
.find(|x| x.dpi() == (parsed_dpi, parsed_dpi)) .page_resolutions()
.expect("DPI not supported by printer"); .find(|x| x.dpi() == (parsed_dpi, parsed_dpi))
.expect("DPI not supported by printer");
builder.merge(supported_dpi).unwrap();
}
// color // color
let parsed_color = capabilities if let Some(color) = &color {
.page_output_colors() let parsed_color = capabilities
.find(|x| { .page_output_colors()
x.as_predefined_name() == Some(color.parse::<PredefinedPageOutputColor>().unwrap()) .find(|x| {
}) x.as_predefined_name() == Some(color.parse::<PredefinedPageOutputColor>().unwrap())
.map(|x| x.as_predefined_name().unwrap()) })
.expect("Invalid color name"); .map(|x| x.as_predefined_name().unwrap())
.expect("Invalid color name");
let supported_color = capabilities
.page_output_colors() let supported_color = capabilities
.find(|x| x.as_predefined_name() == Some(parsed_color)) .page_output_colors()
.expect("Color not supported by printer"); .find(|x| x.as_predefined_name() == Some(parsed_color))
.expect("Color not supported by printer");
builder.merge(supported_color).unwrap();
let mut builder = PrintTicketBuilder::new(&my_device).unwrap();
let default_ticket = default_ticket(quality);
if let Err(e) = builder.merge(default_ticket) {
println!("Failed to merge default ticket: {:?}", e);
} }
builder.merge(a6_media).unwrap();
builder.merge(supported_dpi).unwrap(); // let default_ticket = default_ticket(quality);
builder.merge(supported_color).unwrap(); // if let Err(e) = builder.merge(default_ticket) {
// println!("Failed to merge default ticket: {:?}", e);
// }
let ticket = builder.build().unwrap(); let ticket = builder.build().unwrap();
let xml=ticket.get_xml(); // let xml=ticket.get_xml();
let xml_string = String::from_utf8_lossy(&xml).to_string(); // let xml_string = String::from_utf8_lossy(&xml).to_string();
println!("PrintTicket XML as String: {}", xml_string); // println!("PrintTicket XML as String: {}", xml_string);
return; return;
let path = Path::new(&document_path); let path = Path::new(&document_path);

@ -43,8 +43,8 @@ pub fn img2pdf_from_bytes(img_data: &[u8], dpi: f64) -> io::Result<Vec<u8>> {
((height as f64) / dpi * 72.0).round() as u32, ((height as f64) / dpi * 72.0).round() as u32,
); );
println!("Image size in pixels: {}x{}", width, height); // println!("Image size in pixels: {}x{}", width, height);
println!("Dest Image size in points: {}x{}", page_width, page_height); // println!("Dest Image size in points: {}x{}", page_width, page_height);
let (rgb_img, mask_img) = separate_rgb_and_alpha(img); let (rgb_img, mask_img) = separate_rgb_and_alpha(img);

@ -80,8 +80,8 @@ pub fn get_output_quality_options(xml: &str) -> Vec<String> {
// let mut buf = Vec::new(); // let mut buf = Vec::new();
let mut inside_target_feature = false; let mut inside_target_feature = false;
let mut inside_option = false; // let mut inside_option = false;
let mut current_option_name = String::new(); // let mut current_option_name = String::new();
let mut results = HashSet::new(); let mut results = HashSet::new();
loop { loop {

@ -13,8 +13,8 @@
"windows": [ "windows": [
{ {
"title": "printer", "title": "printer",
"width": 400, "width": 700,
"height": 560 "height": 450
} }
], ],
"security": { "security": {
@ -31,5 +31,10 @@
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
] ]
},
"plugins": {
"fs": {
"requireLiteralLeadingDot": false
}
} }
} }

@ -1,10 +1,17 @@
@import "tailwindcss"; @import "tailwindcss";
main { html{
@apply flex flex-col items-center justify-start min-h-screen gap-4 p-4;
}
main{
@apply w-full min-h-screen;
}
section {
@apply flex flex-col items-center justify-center gap-4 p-4;
@apply bg-gray-100 rounded-xl;
} }
button{ button{
@apply px-4 py-2 bg-fuchsia-400 text-black font-bold rounded hover:bg-fuchsia-500; @apply px-4 py-2 bg-fuchsia-400 text-black font-bold rounded-full hover:bg-fuchsia-500;
} }
select, input[type="file"] { select, input[type="file"] {
@apply px-4 py-2 border border-gray-300 rounded; @apply px-4 py-2 border border-gray-300 rounded;

@ -1,9 +1,10 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { open } from '@tauri-apps/plugin-dialog'; import { open, save } from '@tauri-apps/plugin-dialog';
import "./App.css"; import "./App.css";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { AvailableQualities } from "./params"; import { AvailableQualities } from "./params";
import { listenToPrints, clearPrints, deletePrint, createTestFile } from "./utils/backend";
import { saveToDisk } from "./utils/fs";
const OptionTypes=[ const OptionTypes=[
'dpi_options', 'dpi_options',
@ -11,11 +12,16 @@ const OptionTypes=[
'size_options', 'size_options',
'quality_options', 'quality_options',
] ]
const ENABLE_OPTIONS=false;
function App() { function App() {
const [printer, setPrinter] = useState(""); const [printer, setPrinter] = useState("");
const [selectedFile, setSelectedFile] = useState(); const [selectedFile, setSelectedFile] = useState();
const [playing, setPlaying] = useState(false);
const [printing, setPrinting] = useState();
const [prints, setPrints] = useState([]);
function getPrinters() { function getPrinters() {
invoke("get_all_printers") invoke("get_all_printers")
@ -76,14 +82,21 @@ function App() {
return; return;
} }
invoke("print_document",{ let options = {
deviceName: printer, // Use the selected printer deviceName: printer, // Use the selected printer
documentPath: file, // Use the selected file path documentPath: file, // Use the selected file path
dpi: document.getElementById("dpi_options").value, };
color: document.getElementById("color_options").value, if(ENABLE_OPTIONS) {
size: document.getElementById("size_options").value, options = {
quality: document.getElementById("quality_options").value, ...options,
}) dpi: document.getElementById("dpi_options").value,
color: document.getElementById("color_options").value,
size: document.getElementById("size_options").value,
quality: document.getElementById("quality_options").value,
};
}
invoke("print_document", options)
.then(() => { .then(() => {
console.log("Print job sent successfully."); console.log("Print job sent successfully.");
}) })
@ -104,9 +117,53 @@ function App() {
} }
}); });
} }
function printLatest(){
if(prints.length === 0) {
console.log("No prints available to process.");
return;
}
const latestPrint = prints[prints.length - 1];
console.log("Latest print job:", latestPrint);
setPrinting(latestPrint.id);
// save url to local disk
saveToDisk(latestPrint.id, latestPrint.url)
.then((filePath) => {
console.log("File saved to:", filePath);
invoke("print_document", {
deviceName: printer, // Use the printer from the latest print job
documentPath: filePath, // Use the document path from the latest print job
}).then(() => {
console.log("Print job sent successfully for latest print.");
// TODO: clear print records
deletePrint(latestPrint.id); // Clear the print record after sending the job
console.log("Print record deleted:", latestPrint.id);
setPrinting();
}).catch((error) => {
console.error("Error sending print job for latest print:", error);
});
}).catch((error) => {
console.error("Error saving file:", error);
});
}
useEffect(()=>{ useEffect(()=>{
if(!printer) return; if(!printer) return;
if(!ENABLE_OPTIONS) return;
invoke("get_printer_capabilities", { deviceName: printer }) invoke("get_printer_capabilities", { deviceName: printer })
.then((capabilities) => { .then((capabilities) => {
console.log(`Capabilities for ${printer}:`, capabilities); console.log(`Capabilities for ${printer}:`, capabilities);
@ -137,28 +194,67 @@ function App() {
},[printer]); },[printer]);
useEffect(() => {
if (!playing) return;
printLatest();
}, [prints, playing]);
useEffect(() => { useEffect(() => {
getPrinters(); getPrinters();
listenToPrints((newPrints) => {
console.log("Received prints:", newPrints);
setPrints(newPrints);
});
}, []); }, []);
return ( return (
<main className="container *:w-full"> <main className="grid grid-cols-2 gap-2 p-2">
<button className="!w-auto self-start !bg-fuchsia-200" onClick={getPrinters}>refresh printers</button> <div className="grid grid-rows-2 gap-2">
<select id="printer-select" value={printer} onChange={(e) => setPrinter(e.target.value)}></select> <section className="*:w-full">
<select id="size_options"></select> <button className="!w-auto self-start !bg-amber-200" onClick={getPrinters}>refresh printers</button>
<select id="dpi_options"></select> <select id="printer-select" value={printer} onChange={(e) => setPrinter(e.target.value)}></select>
<select id="color_options"></select> {ENABLE_OPTIONS && <>
<select id="quality_options"></select> <select id="size_options"></select>
<div className="flex flex-row gap-2"> <select id="dpi_options"></select>
<label id="document_path_label" className="flex-1 break-all">{selectedFile ? `${selectedFile}` : "Select a document to print:"}</label> <select id="color_options"></select>
<button onClick={openFile}>open</button> <select id="quality_options"></select>
</>}
</section>
<section className="*:w-full">
<div className="flex flex-row gap-2 items-center">
<label id="document_path_label" className="flex-1 break-all">{selectedFile ? `${selectedFile}` : "Select a document to print:"}</label>
<button onClick={openFile}>open</button>
</div>
<label id="pdf_path_label" className=""></label>
<div className="grid grid-cols-2 gap-2">
<button onClick={convertPdf}>convert PDF</button>
<button onClick={printDocument}>print</button>
</div>
</section>
</div> </div>
<section className="*:w-full">
<button onClick={convertPdf}>convert PDF</button> <div className="border flex-1 p-2 overflow-y-auto overflow-x-hidden text-sm break-all flex flex-col gap-1">
<label id="pdf_path_label" className="flex-1"></label> {prints.length > 0 ? (
prints.map((print) => (
<button onClick={printDocument}>print</button> <div key={print.id} className="bg-green-200 px-2 rounded-full">{print.id}</div>
))
) : (
<p>No prints available</p>
)}
</div>
<div className="grid grid-cols-2 gap-2">
<button onClick={()=>setPlaying(!playing)} className={playing? '!bg-green-300':''}>{playing? 'pause':'play'}</button>
<button onClick={clearPrints}>clear all</button>
<button onClick={createTestFile}>create test file</button>
</div>
<p className="h-[4rem]">{printing? `Printing document with id ${printing}` : "No document is being printed."}</p>
</section>
</main> </main>
); );
} }

@ -0,0 +1,77 @@
import { initializeApp } from "firebase/app";
import { getFirestore, collection, doc, deleteDoc, onSnapshot, setDoc } from "firebase/firestore";
const firebaseConfig = {
apiKey: "AIzaSyD1VzUKXt0JskwyfjfIAbdROzPNB3fTIw0",
authDomain: "uc-24070-thegreattipsy.firebaseapp.com",
projectId: "uc-24070-thegreattipsy",
storageBucket: "uc-24070-thegreattipsy.firebasestorage.app",
messagingSenderId: "772804793020",
appId: "1:772804793020:web:258003100900c20e0fb6b9",
measurementId: "G-1CRHMJY4L9"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const CollectionName="prints";
export function listenToPrints(callback) {
// Listen for changes in the prints collection
const db = getFirestore(app);
const printsRef = collection(db, CollectionName);
onSnapshot(printsRef, (snapshot) => {
const prints = [];
snapshot.forEach((doc) => {
prints.push({ id: doc.id, ...doc.data() });
});
callback(prints);
});
}
export function clearPrints() {
// Clear the prints collection
const db = getFirestore(app);
const printsRef = collection(db, CollectionName);
onSnapshot(printsRef, (snapshot) => {
snapshot.forEach((doc) => {
// Assuming you have a delete function to remove documents
deleteDoc(doc.ref);
});
});
}
export function deletePrint(id) {
// Delete a specific print by id
const db = getFirestore(app);
const printRef = doc(collection(db, CollectionName), id);
deleteDoc(printRef)
.then(() => {
console.log(`Print with id ${id} deleted successfully.`);
})
.catch((error) => {
console.error(`Error deleting print with id ${id}:`, error);
});
}
export function createTestFile(){
// Create a test print file
const db = getFirestore(app);
const testPrint = {
url: "https://s3.ap-northeast-2.amazonaws.com/ultracombos.project/24070-%E5%BE%AE%E9%86%BA%E5%A4%A7%E9%A3%AF%E5%BA%97%E9%AB%98%E9%9B%84%E7%89%88/Postcard-01.png",
createdAt: new Date().toISOString(),
id: `test-${Date.now()}`,
status: "pending"
};
// Add the test print to the collection
setDoc(doc(db, CollectionName, testPrint.id), testPrint)
.then(() => {
console.log("Test print created successfully.");
})
.catch((error) => {
console.error("Error creating test print:", error);
});
}

@ -0,0 +1,29 @@
import { writeFile, BaseDirectory, exists, mkdir } from '@tauri-apps/plugin-fs';
import { path } from '@tauri-apps/api';
const OutputDir = 'prints';
export async function saveToDisk(id, url){
console.log("Saving file to disk:", id, url);
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const base=BaseDirectory.Document;
const folder=`${OutputDir}`;
const filePath = `${folder}/${id}.png`;
// Check if the directory exists, if not create it
const dirExists = await exists(folder, { baseDir: base });
if (!dirExists) {
console.log("Directory does not exist, creating:", folder);
await mkdir(folder, { baseDir: base });
}
// Write the file to the disk
await writeFile(filePath, arrayBuffer, { baseDir: base });
return `${await path.documentDir()}\\${filePath}`;
}
Loading…
Cancel
Save