Skip to content
This repository was archived by the owner on Nov 10, 2022. It is now read-only.

Commit e04e6c1

Browse files
committed
feat: add source code
1 parent ed49e5e commit e04e6c1

8 files changed

+276
-0
lines changed

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Directories
2+
dist/
3+
.vscode/
4+
node_modules/
5+
6+
# Files
7+
config.json
8+
package-lock.json

build/icon.png

10.5 KB
Loading

config-example.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"tokens": {
3+
"cloudinary": {
4+
"key": "",
5+
"secret": ""
6+
},
7+
"supabase": {
8+
"url": "",
9+
"key": ""
10+
},
11+
"opencage": ""
12+
}
13+
}

index.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const config = require('./config.json');
2+
3+
const { app, BrowserWindow, dialog, ipcMain } = require('electron');
4+
5+
// image
6+
const sharp = require('sharp');
7+
const exif = require('exifr');
8+
const fs = require('fs');
9+
const crypto = require('crypto');
10+
11+
// api
12+
const cloudinary = require('cloudinary');
13+
const { createClient } = require('@supabase/supabase-js');
14+
const supabase = createClient(config.tokens.supabase.url, config.tokens.supabase.key);
15+
16+
cloudinary.config({
17+
cloud_name: 'mue',
18+
api_key: config.tokens.cloudinary.key,
19+
api_secret: config.tokens.cloudinary.secret
20+
});
21+
22+
app.whenReady().then(() => {
23+
const win = new BrowserWindow({
24+
width: 1000,
25+
height: 800,
26+
title: 'Mue Uploader',
27+
webPreferences: {
28+
nodeIntegration: true,
29+
contextIsolation: false
30+
}
31+
});
32+
33+
win.loadFile('public/index.html');
34+
});
35+
36+
// ipc
37+
let file;
38+
ipcMain.on('addFile', async (event) => {
39+
const filePath = dialog.showOpenDialogSync({ properties: ['openFile'] });
40+
const metadata = await exif.parse(filePath[0]);
41+
file = filePath[0];
42+
event.sender.send('addedFile', metadata, filePath[0]);
43+
});
44+
45+
ipcMain.on('upload', (event, arg) => {
46+
const filename = crypto.randomBytes(8).toString('hex');
47+
// compress
48+
sharp(fs.readFileSync(file), { quality: 85, reductionEffort: 6 })
49+
.toFile(`./${filename}.webp`, (err) => {
50+
if (err) {
51+
return event.sender.send('message', 'Failed to compress image');
52+
}
53+
54+
// upload
55+
cloudinary.v2.uploader.upload(`./${filename}.webp`, {
56+
folder: 'photos/' + arg.category.toLowerCase()
57+
}, async (err) => {
58+
fs.unlinkSync(`./${filename}.webp`);
59+
if (err) {
60+
return event.sender.send('message', 'Failed to upload image');
61+
}
62+
63+
// add to db
64+
const { error } = await supabase
65+
.from('newimages')
66+
.insert([{
67+
filename: filename,
68+
photographer: arg.photographer,
69+
category: arg.category,
70+
location: arg.location,
71+
camera: arg.camera_model
72+
}]);
73+
74+
if (error) {
75+
event.sender.send('message', 'Failed to add image to database');
76+
} else {
77+
event.sender.send('message', 'Success');
78+
}
79+
});
80+
});
81+
});

package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "mue-uploader",
3+
"author": "David Ralph",
4+
"version": "1.0.0",
5+
"description": "Internal uploading utility for Mue",
6+
"scripts": {
7+
"start": "electron .",
8+
"dist": "electron-builder"
9+
},
10+
"dependencies": {
11+
"@supabase/supabase-js": "^1.18.1",
12+
"cloudinary": "1.26.2",
13+
"exifr": "^7.1.2",
14+
"sharp": "^0.28.3"
15+
},
16+
"devDependencies": {
17+
"electron": "13.1.6",
18+
"electron-builder": "^22.11.7"
19+
}
20+
}

public/index.css

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@import url('https://fonts.googleapis.com/css2?family=Lexend+Deca&display=swap');
2+
3+
* {
4+
font-family: 'Lexend Deca' !important;
5+
}
6+
7+
img {
8+
height: 128px;
9+
width: auto;
10+
}

public/index.html

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<link rel='stylesheet' href='index.css'>
5+
</head>
6+
<body>
7+
<h1>Uploader</h1>
8+
<div id='photodiv'>
9+
<img id='photo'>
10+
<br/>
11+
</div>
12+
<button onClick='addFile()'>Add File</button>
13+
<br/><br/>
14+
<label for='category'>Category:</label>
15+
<select name='category' id='category'>
16+
<option>Other</option>
17+
</select>
18+
<br/><br/>
19+
<div id='newcategorydiv'>
20+
<label for='newcategory'>New Category:</label>
21+
<input type='text' id='newcategory' name='newcategory'>
22+
<br/><br/>
23+
</div>
24+
<label for='photographer'>Photographer:</label>
25+
<select name='photographer' id='photographer'>
26+
<option>Other</option>
27+
</select>
28+
<br/><br/>
29+
<div id='newphotographerdiv'>
30+
<label for='newphotographer'>New Photographer:</label>
31+
<input type='text' id='newphotographer' name='newphotographer'>
32+
<br/><br/>
33+
</div>
34+
<label for='location'>Location:</label>
35+
<input type='text' id='location' name='location'>
36+
<br/><br/>
37+
<label for='model'>Camera Model:</label>
38+
<input type='text' id='model' name='model'>
39+
<br/><br/>
40+
<button onClick='upload()'>Upload</button>
41+
<br/><br/>
42+
<span id='message'></span>
43+
<script src='index.js'></script>
44+
</body>
45+
</html>

public/index.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const { ipcRenderer } = require('electron');
2+
const config = require('../config.json');
3+
4+
const addFile = () => {
5+
document.getElementById('message').innerText = '';
6+
ipcRenderer.send('addFile');
7+
}
8+
9+
const photodiv = document.getElementById('photodiv');
10+
photodiv.style.display = 'none';
11+
12+
ipcRenderer.on('addedFile', async (_event, arg, arg2) => {
13+
photodiv.style.display = 'block';
14+
document.getElementById('photo').src = arg2;
15+
document.getElementById('model').value = arg.Model || 'Unknown';
16+
17+
// find location from gps data
18+
if (arg.latitude) {
19+
const res = await fetch(`https://api.opencagedata.com/geocode/v1/json?q=${arg.latitude},${arg.longitude}&key=${config.tokens.opencage}`);
20+
const location = await res.json();
21+
document.getElementById('location').value = location.results[0].components.town + ', ' + location.results[0].components.country;;
22+
}
23+
});
24+
25+
// list of photographers
26+
const getPhotographers = async () => {
27+
const res = await fetch('https://api.muetab.com/images/photographers');
28+
const photographers = await res.json();
29+
const dropdown = document.getElementById('photographer');
30+
// in the future, we probably want to make the api sort alphabetically instead of doing it here
31+
photographers.sort().forEach((element) => {
32+
const option = document.createElement('option');
33+
option.text = element;
34+
dropdown.add(option);
35+
});
36+
}
37+
38+
getPhotographers();
39+
40+
// has the user selected this option
41+
const newphotographer = document.getElementById('newphotographerdiv');
42+
43+
document.getElementById('photographer').onchange = (e) => {
44+
if (e.target.value === 'Other') {
45+
newphotographer.style.display = 'block';
46+
} else {
47+
newphotographer.style.display = 'none';
48+
}
49+
}
50+
51+
/// copy and pasted from above
52+
const getCategories = async () => {
53+
const res = await fetch('https://api.muetab.com/images/categories');
54+
const categories = await res.json();
55+
const dropdown = document.getElementById('category');
56+
categories.forEach((element) => {
57+
const option = document.createElement('option');
58+
option.text = element.charAt(0).toUpperCase() + element.slice(1);
59+
dropdown.add(option);
60+
});
61+
}
62+
63+
getCategories();
64+
65+
const newcategory = document.getElementById('newcategorydiv');
66+
67+
document.getElementById('category').onchange = (e) => {
68+
if (e.target.value === 'Other') {
69+
newcategory.style.display = 'block';
70+
} else {
71+
newcategory.style.display = 'none';
72+
}
73+
}
74+
75+
const upload = async () => {
76+
document.getElementById('message').innerText = '';
77+
78+
// these 2 if statements are for seeing if the user used the new options
79+
let photographer = document.getElementById('photographer').value;
80+
if (document.getElementById('newphotographerdiv').style.display === 'block') {
81+
photographer = document.getElementById('newphotographer').value;
82+
}
83+
84+
let category = document.getElementById('category').value;
85+
if (document.getElementById('newcategorydiv').style.display === 'block') {
86+
category = document.getElementById('newcategory').value;
87+
}
88+
89+
ipcRenderer.send('upload', {
90+
photographer: photographer,
91+
category: category,
92+
location: document.getElementById('location').value,
93+
camera_model: document.getElementById('model').value
94+
});
95+
}
96+
97+
ipcRenderer.on('message', (_event, arg) => {
98+
document.getElementById('message').innerText = arg;
99+
});

0 commit comments

Comments
 (0)