Updated smart form

This commit is contained in:
Andrew Gundersen 2022-04-04 08:02:16 -05:00
commit 78eb4050ae
4 changed files with 109 additions and 62 deletions

View file

@ -2,8 +2,9 @@ const exec = require('child_process').exec;
const sign = require("electron-osx-sign").signAsync; const sign = require("electron-osx-sign").signAsync;
const notarize = require("electron-notarize").notarize; const notarize = require("electron-notarize").notarize;
const app = "Crimata.app"; const name = "Crimata";
const dir = `${app}/Contents/Resources/app`; const dir = `${name}.app/Contents/Resources/app`;
const registry = "https://gitlab.com/api/v4/projects/25637892/packages/generic/crimata/0.0.1/Crimata.zip";
const runShellCommand = (cmd) => (new Promise((resolve, reject) => { const runShellCommand = (cmd) => (new Promise((resolve, reject) => {
exec(cmd, (error, stdout, stderr) => { exec(cmd, (error, stdout, stderr) => {
@ -11,8 +12,11 @@ const runShellCommand = (cmd) => (new Promise((resolve, reject) => {
}); });
})); }));
// /projects/:id/packages/generic/:package_name/:package_version/:file_name?status=:status
const signConfig = { const signConfig = {
"app": app, "app": name + ".app",
"hardened-runtime": true, "hardened-runtime": true,
"gatekeeper-assess": false, "gatekeeper-assess": false,
"signature-flags": "library", "signature-flags": "library",
@ -21,7 +25,7 @@ const signConfig = {
}; };
const notarizeConfig = { const notarizeConfig = {
appPath: app, appPath: name + ".app",
appleId: "gundersena@crimata.com", appleId: "gundersena@crimata.com",
appBundleId: "com.crimata.CrimataMessenger", appBundleId: "com.crimata.CrimataMessenger",
appleIdPassword: process.env["AC_PASSWORD"] appleIdPassword: process.env["AC_PASSWORD"]
@ -31,18 +35,24 @@ const notarizeConfig = {
try try
{ {
console.log("Building..."); // console.log("Building...");
await runShellCommand(`rm -rf ${dir} && mkdir ${dir} && cp -r {package.json,src,node_modules} ${dir} // await runShellCommand(`rm -rf ${dir} && mkdir ${dir} && cp -r {package.json,src,node_modules} ${dir}
`); // `);
console.log("Cleaning..."); // console.log("Cleaning...");
await runShellCommand(`xattr -cr ${app}`); // await runShellCommand(`xattr -cr ${name}.app`);
console.log("Signing..."); // console.log("Signing...");
await sign(signConfig); // await sign(signConfig);
console.log("Notarizing..."); // console.log("Notarizing...");
await notarize(notarizeConfig); // await notarize(notarizeConfig);
// console.log("Compressing...");
// await runShellCommand(`zip -vr ${name}.zip ${name}.app`);
console.log("Uploading to Gitlab...");
await runShellCommand(`curl --header "PRIVATE-TOKEN: ${process.env["API_TOKEN"]}" --upload-file ${name}.zip "${registry}"`);
} }
catch (e) catch (e)
{ {
@ -50,3 +60,10 @@ const notarizeConfig = {
} }
})(); })();
/**
* Copy file from local to remote:
*
* scp -r Electron.app gundersena@crimata-server:~/Desktop/crimata/website/assets
*/

View file

@ -0,0 +1,23 @@
export const isEmail = (value) => {
console.log(value);
if (!/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(value)) {
return "Not a valid email."
}
}
export const isPassword = (value) => {
if (!/^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{6,16}$/.test(value)) {
return "Password must have symbol, number, uppercase and be 6-16 length.";
}
}
/**
* Name is optional and therefore doesn't return error if no value.
*/
export const isName = (value) => {
if (value) {
if (!/^([a-zA-Z ]){5,30}$/.test(value)) {
return "Name must be at least 5 long.";
}
}
}

View file

@ -1,83 +1,86 @@
// Basic debounce implentation
const debounce = (fn, delay) => { const debounce = (fn, delay) => {
let timeoutId; let timeoutId;
return (...args) => {
const wrapper = (...args) => { if (timeoutId) clearTimeout(timeoutId);
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
fn(...args); fn(...args);
}, delay); }, delay);
} }
return wrapper;
} }
/**
* Dynamic form component with auto submission and validation.
const SmartForm = *
* props
* .fields => List of field objects
* .channel => e.g. "/login"
* .modifier => Form description
*/
const SmartForm =
{ {
props: ["fields", "channel", "modifier"], props: ["fields", "channel", "modifier"],
setup(props) setup(props)
{ {
let logsEl; // Error log below form
const log = Vue.ref(props.modifier);
const submit = debounce(async (data) => { /**
* Valid and submit form data to channel.
*/
const submit = async () => {
let object = {}; // Validate each field
data.forEach(function(value, key){ for (let field of props.fields) {
object[key] = value; const error = field.valid(field.value);
}); if (error) return error;
const err = await window.mainApi.invoke(props.channel, object)
if (err) {
logsEl.innerText = err;
logsEl.style.color = "red";
return;
} }
logsEl.style.color = "green"; // Reduce fields into the form
logsEl.innerText = props.modifier; const form = props.fields.reduce((v, n) => {
v[n.name] = n.value;
return v;
}, {});
console.log("Submitting =>", form);
return await window.mainApi.invoke(props.channel, form);
}
/**
* Wrapper for submit that is debounced and updates UI elements
*/
const preSub = debounce(async () => {
const error = await submit();
log.value = error ? error : props.modifier
}, 2000); }, 2000);
Vue.onMounted(() => { return { preSub, log };
const el = document.getElementById("smart-form");
logsEl = el.querySelector(".smart-form-logs");
logsEl.innerText = props.modifier;
el.addEventListener("input", () => {
submit(new FormData(el))
});
});
}, },
template: ` template: `
<form id="smart-form" class="smart-form"> <form id="smart-form" class="smart-form" v-on:input="preSub">
<div v-for="field in fields" style="display:flex"> <div v-for="field in fields" style="display:flex">
<input type="text" class="text-field" <input type="text" class="text-field"
v-model="field.value"
:name="field.name" :name="field.name"
:value="field.value"
:placeholder="field.placeholder" :placeholder="field.placeholder"
/> />
</div> </div>
<div class="smart-form-logs"></div> <div class="smart-form-logs">{{ log }}</div>
</form>` </form>`
} }
export default SmartForm; export default SmartForm;

View file

@ -1,4 +1,5 @@
import SmartForm from "./form.js"; import SmartForm from "./form.js";
import { isEmail, isPassword, isName } from "./control/valid.js";
const Login = const Login =
{ {
@ -8,7 +9,7 @@ const Login =
setup() setup()
{ {
return; return { isEmail, isPassword, isName };
}, },
template: ` template: `
@ -19,16 +20,19 @@ const Login =
name: 'email', name: 'email',
value: null, value: null,
placeholder: 'Email', placeholder: 'Email',
valid: isEmail
}, },
{ {
name: 'password', name: 'password',
value: null, value: null,
placeholder: 'Password' placeholder: 'Password',
valid: isPassword
}, },
{ {
name: 'name', name: 'name',
value: null, value: null,
placeholder: 'Name' placeholder: 'Name',
valid: isName
}]" }]"
:channel="'auth'" :channel="'auth'"
:modifier="'Login or enter name to register.'" :modifier="'Login or enter name to register.'"
@ -37,4 +41,4 @@ const Login =
</div>` </div>`
} }
export default Login; export default Login;