mit neuen venv und exe-Files

This commit is contained in:
2024-11-03 17:26:54 +01:00
parent 07c05a338a
commit 0c373ff593
15115 changed files with 1998469 additions and 0 deletions

View File

@@ -0,0 +1,184 @@
@font-face {
font-family: 'Nunito';
src: url('../Nunito-Light.ttf');
}
@font-face {
font-family: 'Vazirmatn';
src: url('../Vazirmatn-Light.ttf');
font-style: normal;
font-weight: 400;
font-display: swap;
unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
}
:root {
--background: #fbfbfb;
--primary: #458bc6;
--primary-darker: #1c79c7;
--primary-transparent: #1c79c71a;
--error: red;
--unselected: lightgrey;
--disabled: #808080;
--text: #000000;
--title: #666666;
--warning-background: #fff3cd;
--warning-border: #a0987c;
--warning-text: #000000;
--border-radius: 4px;
}
.dark-theme {
--background: #15131e;
--unselected: #5f5f5f;
--text: #ffffff;
--title: #e2e2e2;
--warning-background: #ffdc6d;
}
* {
box-sizing: border-box;
color: var(--text);
}
body {
background-color: var(--background);
font-family: 'Vazirmatn', 'Nunito', Helvetica, Arial, sans-serif;
font-weight: 100;
margin: 0 18px 10px 18px;
}
.mid {
/* Global center alignment */
margin: auto;
max-width: 800px;
}
/* Headers */
h2 {
font-weight: normal;
font-size: 25px;
margin: 10px 0 2px 0;
}
h3 {
font-size: 17px;
margin: 10px 0 4px 0;
}
.sub_header {
/* Headers in tabs */
font-size: 17px;
margin: 10px 2px 4px 2px;
}
h2 > small {
font-size: 13px;
}
/* Generic inputs */
button,
input,
select {
border: 1px solid var(--primary);
border-radius: var(--border-radius);
padding: 4px;
font-family: 'Vazirmatn', 'Nunito', Helvetica, Arial, sans-serif;
font-weight: 100;
}
input,
select,
textarea,
select option {
background-color: var(--background);
}
input:focus {
outline: none; /* Don't show outline so you can see the colour change */
}
button {
cursor: pointer;
border-radius: var(--border-radius);
background: transparent;
padding: 3px 8px;
transition: border 0.3s, background 0.3s;
border-style: solid;
border-width: 2px;
}
button:not(.selected):not(.unselected):hover {
/* Apply hovers to non-state buttons */
background: var(--primary-transparent);
}
button.selected,
button.unselected:hover {
border-color: var(--primary);
}
button.unselected {
border-color: var(--unselected);
color: var(--unselected);
}
button.large {
border-width: 3px;
padding: 8px;
}
/* Info icon */
.info_icon {
/* Information icon */
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAq0lEQVR4XnWQywrDIBBF82P+WPtXebiSbJKtq0jAtVAEwW0WIZ16p04aWjpwcbge5tU0Nfq+V13XteV9QDVX8i/Qrei5LAtt28ZCXuADf9dKxzzPhEgpsRDwKqwatCiiaZrIe09aazLGMOicAwi1qIh5xGAw50z7vtM4juyB+QFDCFzNWnt6DEpr0bquPMIwDKcnrXkZMWOMrAv0XqZufhf4C/ycR6JW/nvwF51vE+IcomqCAAAAAElFTkSuQmCC) -0px -0px
no-repeat;
height: 10px;
width: 10px;
overflow: hidden;
margin-left: 0.25em;
vertical-align: middle;
display: inline-block;
}
/* Small notes */
.note {
font-size: 14px;
font-style: italic;
margin: 8px 0 0 0;
}
/* Filepath-browse layout */
.filepath-browse-layout {
display: grid;
grid-gap: 4px;
grid-template-columns: 1fr 120px;
}
/* Icon-specific */
#icon-invalid-warning {
font-size: 14px;
padding-top: 4px;
}
/* Utils */
.noselect {
/* Don't select tab text */
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
}

View File

@@ -0,0 +1,363 @@
/* Header */
#header {
display: grid;
grid-template-columns: auto 1fr;
margin-top: 18px;
}
#header .title {
display: flex;
align-items: center;
}
#header .title img {
height: 41px;
}
#header .title h1 {
font-weight: 100;
font-size: 30px;
color: var(--title);
margin: 0 0 0 10px;
}
#header .title > a {
display: inherit;
text-decoration: none;
}
#header .title > a:hover {
-webkit-mask-image: linear-gradient(-75deg, rgb(0, 0, 0) 30%, rgba(0, 0, 0, 0.5) 50%, rgb(0, 0, 0) 70%);
-webkit-mask-size: 200%;
animation: shine 2s;
animation-fill-mode: forwards;
}
@-webkit-keyframes shine {
from {
-webkit-mask-position: 150%;
}
to {
-webkit-mask-position: -50%;
}
}
#header .extra-links {
display: flex;
flex-direction: column;
text-align: right;
}
#header .extra-links a {
filter: grayscale(1);
transition: filter 0.3s;
text-decoration: none;
}
#header .extra-links a span {
font-size: 15px;
color: var(--primary);
}
#header .extra-links a:hover {
filter: grayscale(0);
}
#header .extra-links a img {
height: 20px;
vertical-align: text-bottom;
}
#header .extra-links a:not(:first-child) img {
margin-top: 4px;
}
#header .ui-config {
margin-top: 4px;
display: flex;
align-items: center;
gap: 4px;
justify-content: flex-end;
}
#header .ui-config [for='language-selection'] {
line-height: 0;
}
#header .ui-config #language-selection {
padding: 0 6px;
}
#header .ui-config #theme-toggle {
cursor: pointer;
user-select: none;
display: flex;
height: 18px;
}
#language-selection {
max-width: 200px;
}
/* Warnings */
#warnings {
font-size: 13px;
}
#warnings > div {
background: var(--warning-background);
border: 1px solid var(--warning-border);
border-radius: var(--border-radius);
margin: 10px 0;
padding: 8px;
}
#warnings > div > p {
margin: 0;
white-space: pre-wrap;
}
#warnings > div > p,
#warnings > div > p a {
color: var(--warning-text);
}
/* Sections */
div[id*='section'] {
margin-top: 12px;
}
div[id*='section'] .header {
display: flex;
cursor: pointer;
}
div[id*='section'] .header img {
height: 30px;
transition: transform 0.4s;
transform: rotate(180deg);
}
div[id*='section'] .header h2 {
display: inline;
margin: 0 0 0 15px;
}
div[id*='section'] .content {
display: none; /* Hide sections by default */
margin: 5px 10px 0 10px;
}
/* Additional Files */
#datas-add-buttons {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 5px;
}
/* Advanced tab */
.option-container {
/* Option containers */
margin-top: 4px;
}
.option-container > span {
/* Option name */
margin-right: 10px;
}
.option-container.multiple-input > img {
height: 23px;
vertical-align: middle;
cursor: pointer;
}
.option-container.multiple-input > div {
margin-left: 20px;
}
.option-container.multiple-input > div > div,
#datas-list > div {
display: grid;
grid-template-columns: 1fr auto;
grid-gap: 4px;
margin-top: 2px;
}
.option-container.multiple-input > div > div.dual-value,
#datas-list > div {
grid-template-columns: 1fr 1fr auto;
}
.option-container.multiple-input > div > div > img,
#datas-list > div > img {
height: 100%;
padding: 2px 0;
cursor: pointer;
}
.option-container.input {
display: grid;
grid-template-columns: auto 1fr;
grid-gap: 5px;
}
.option-container.input.with-browse {
grid-template-columns: auto 1fr auto;
}
/* Current Command */
#current-command textarea {
width: 100%;
}
/* Output */
#output {
display: none; /* Hidden by default */
}
#output.show {
display: block;
}
#output textarea {
width: 100%;
white-space: pre;
overflow-y: hidden;
}
#output textarea.failure {
border-color: var(--error);
}
/* Common issues link formatting */
#common-issue-link {
font-size: 12px;
text-align: center;
margin-bottom: 5px;
display: none; /* Default to not shown */
}
#common-issue-link.show {
display: block;
}
#common-issue-link a {
text-decoration: none;
color: var(--primary);
}
#common-issue-link a:hover {
color: var(--primary-darker);
}
/* Package / Open Output Folder Buttons */
#package-button-wrapper {
display: flex;
justify-content: space-evenly;
}
#package-button,
#open-output-folder-button {
width: 100%;
background-color: var(--primary);
border: 1px solid var(--primary);
font-size: 15px;
color: white;
height: 38px;
padding: 0 30px;
text-align: center;
box-sizing: border-box;
letter-spacing: 0.1rem;
text-transform: uppercase;
white-space: nowrap;
transition: background-color 0.3s;
}
#package-button:hover,
#open-output-folder-button:hover {
background-color: var(--primary-darker);
}
#package-button:disabled {
background-color: var(--disabled);
border-color: var(--disabled);
cursor: not-allowed;
}
#open-output-folder-button {
display: none; /* Default to not shown */
margin-left: 4px;
}
#open-output-folder-button.show {
display: block;
}
/* Loading spinner (from https://projects.lukehaas.me/css-loaders/) */
.loading-spinner-wrapper {
display: flex;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100vh;
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 75%);
}
.loading-label {
color: white;
}
.loading-spinner,
.loading-spinner:after {
border-radius: 50%;
width: 8em;
height: 8em;
}
.loading-spinner {
margin: 60px auto;
font-size: 10px;
position: relative;
text-indent: -9999em;
border-top: 1.1em solid rgba(256, 256, 256, 0.2);
border-right: 1.1em solid rgba(256, 256, 256, 0.2);
border-bottom: 1.1em solid rgba(256, 256, 256, 0.2);
border-left: 1.1em solid #ffffff;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation: load8 1.1s infinite linear;
animation: load8 1.1s infinite linear;
}
@-webkit-keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,62 @@
:root {
--modal-offset: 0;
--modal-padding: 200px;
--modal-fallback-color: rgba(0, 0, 0, 0.4);
--modal-coverage-area: 100%;
}
.modal-coverage {
position: fixed;
z-index: 1;
padding-top: var(--modal-padding);
left: var(--modal-offset);
top: var(--modal-offset);
width: var(--modal-coverage-area);
height: var(--modal-coverage-area);
overflow: auto;
background-color: var(--modal-fallback-color);
}
.modal-coverage-hidden {
display: none;
}
.modal-content {
background-color: var(--background);
margin: auto;
padding: 16px;
border: 2px solid var(--primary);
border-radius: var(--border-radius);
width: 80%;
max-width: 600px;
}
.close-btn {
color: var(--primary);
float: right;
font-size: 20px;
font-weight: bold;
}
.close-btn:hover,
.close-btn:focus {
color: var(--primary-darker);
text-decoration: none;
cursor: pointer;
}
.modal-section {
padding: 4px;
}
.modal-header h2 {
margin: 0;
}
.modal-footer {
padding-top: 8px;
}
.modal-btn {
margin-right: 4px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
style="color: #458BC6;">
<path fill="currentColor"
d="M232.5 163.5l122.8 122.8c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L224 234.2l-91.7 91.7c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l122.8-122.8c4.7-4.7 12.3-4.7 17 0zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z" />
</svg>

After

Width:  |  Height:  |  Size: 540 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path fill="currentColor" d="M283.211 512c78.962 0 151.079-35.925 198.857-94.792 7.068-8.708-.639-21.43-11.562-19.35-124.203 23.654-238.262-71.576-238.262-196.954 0-72.222 38.662-138.635 101.498-174.394 9.686-5.512 7.25-20.197-3.756-22.23A258.156 258.156 0 0 0 283.211 0c-141.309 0-256 114.511-256 256 0 141.309 114.511 256 256 256z"/></svg>

After

Width:  |  Height:  |  Size: 416 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
style="color: green;">
<path fill="currentColor"
d="M352 240v32c0 6.6-5.4 12-12 12h-88v88c0 6.6-5.4 12-12 12h-32c-6.6 0-12-5.4-12-12v-88h-88c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h88v-88c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v88h88c6.6 0 12 5.4 12 12zm96-160v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z" />
</svg>

After

Width:  |  Height:  |  Size: 549 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
style="color: red;">
<path fill="currentColor"
d="M108 284c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h232c6.6 0 12 5.4 12 12v32c0 6.6-5.4 12-12 12H108zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z" />
</svg>

After

Width:  |  Height:  |  Size: 444 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" style="color: white;"><path fill="currentColor" d="M256 160c-52.9 0-96 43.1-96 96s43.1 96 96 96 96-43.1 96-96-43.1-96-96-96zm246.4 80.5l-94.7-47.3 33.5-100.4c4.5-13.6-8.4-26.5-21.9-21.9l-100.4 33.5-47.4-94.8c-6.4-12.8-24.6-12.8-31 0l-47.3 94.7L92.7 70.8c-13.6-4.5-26.5 8.4-21.9 21.9l33.5 100.4-94.7 47.4c-12.8 6.4-12.8 24.6 0 31l94.7 47.3-33.5 100.5c-4.5 13.6 8.4 26.5 21.9 21.9l100.4-33.5 47.3 94.7c6.4 12.8 24.6 12.8 31 0l47.3-94.7 100.4 33.5c13.6 4.5 26.5-8.4 21.9-21.9l-33.5-100.4 94.7-47.3c13-6.5 13-24.7.2-31.1zm-155.9 106c-49.9 49.9-131.1 49.9-181 0-49.9-49.9-49.9-131.1 0-181 49.9-49.9 131.1-49.9 181 0 49.9 49.9 49.9 131.1 0 181z"/></svg>

After

Width:  |  Height:  |  Size: 722 B

View File

@@ -0,0 +1,280 @@
<!DOCTYPE html>
<html>
<head>
<title>Auto Py To Exe</title>
<script>
// Provided for type checking
window.eel = {
initialise: () => ({
filename: null,
options: [],
suppliedUiConfiguration: {},
warnings: [],
pathSeparator: '',
defaultOutputFolder: '',
languageHint: '',
}),
does_file_exist: (path) => false,
does_folder_exist: (path) => false,
ask_file: (file_type) => '',
ask_files: () => [],
ask_folder: () => '',
is_file_an_ico: (file_path) => null,
convert_path_to_absolute: (path) => '',
open_output_in_explorer: (output_directory, input_filename, is_one_file) => {},
will_packaging_overwrite_existing: (file_path, manual_name, one_file, output_folder) => true,
package: (command, non_pyinstaller_options) => {},
import_configuration: () => {},
export_configuration: (configuration) => {},
};
</script>
<script type="text/javascript" src="/eel.js"></script>
<script type="text/javascript" src="/js/constants.js"></script>
<script type="text/javascript" src="/js/i18n.js"></script>
<script type="text/javascript" src="/js/initialise.js"></script>
<script type="text/javascript" src="/js/configuration.js"></script>
<script type="text/javascript" src="/js/staticEvents.js"></script>
<script type="text/javascript" src="/js/interface.js"></script>
<script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="/js/modal.js"></script>
<script type="text/javascript" src="/js/messages.js"></script>
<script type="text/javascript" src="/js/packaging.js"></script>
<script type="text/javascript" src="/js/importExport.js"></script>
<link rel="stylesheet" href="/css/general.css" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/modal.css" />
</head>
<body>
<div class="mid">
<div id="header">
<div class="title">
<a href="https://github.com/brentvollebregt/auto-py-to-exe" target="_blank"><img src="/favicon.ico" /></a>
<a href="https://github.com/brentvollebregt/auto-py-to-exe" target="_blank"><h1>Auto Py to Exe</h1></a>
</div>
<div>
<div class="extra-links">
<a href="https://github.com/brentvollebregt/auto-py-to-exe" target="_blank">
<span>GitHub</span>
<img src="https://github.githubassets.com/favicons/favicon.png" alt="GitHub favicon" />
</a>
<a
href="https://nitratine.net/blog/post/issues-when-using-auto-py-to-exe/?utm_source=auto_py_to_exe&utm_medium=application_link&utm_campaign=auto_py_to_exe_help&utm_content=top"
target="_blank"
>
<span data-i18n="ui.links.helpPost">Help Post</span>
<img src="https://nitratine.net/static/img/favicon-384x384.png" alt="Nitratine favicon" />
</a>
</div>
<div class="ui-config">
<label for="language-selection">
<small data-i18n="ui.title.language">Language</small><small>:</small>
</label>
<select id="language-selection"></select>
<span id="theme-toggle">
<img src="img/sun.svg" id="on-dark-theme-button" style="display: none" />
<img src="img/moon.svg" id="on-light-theme-button" style="display: inline" />
</span>
</div>
</div>
</div>
<div id="warnings"></div>
<div>
<h2 data-i18n="ui.title.scriptLocation">Script Location</h2>
<div class="filepath-browse-layout">
<input
id="entry-script"
placeholder="Path to file"
required
data-i18n_placeholder="ui.placeholders.pathToFile"
/>
<button id="entry-script-search" data-i18n="ui.button.browse">Browse</button>
</div>
</div>
<div>
<h2>
<span data-i18n="ui.title.oneFile">Onefile</span>
<small>(--onedir / --onefile)</small>
</h2>
<div>
<button id="one-directory-button" class="large" data-i18n="ui.button.oneDirectory">One Directory</button>
<button id="one-file-button" class="large" data-i18n="ui.button.oneFile">One File</button>
</div>
</div>
<div>
<h2>
<span data-i18n="ui.title.consoleWindow">Console Window</span>
<small>(--console / --windowed)</small>
</h2>
<div>
<button id="console-based-button" class="large" data-i18n="ui.button.consoleBased">Console Based</button>
<button id="window-based-button" class="large" data-i18n="ui.button.windowBased">
Window Based (hide the console)
</button>
</div>
</div>
<div id="section-icon">
<div class="header noselect" onclick="expandSection('icon')">
<img src="img/chevron-square-up.svg" alt="Icon Section Chevron" />
<h2>
<span data-i18n="ui.title.icon">Icon</span>
<small>(--icon)</small>
</h2>
</div>
<div class="content">
<div class="filepath-browse-layout">
<input id="icon-path" placeholder=".ico file" data-i18n_placeholder="ui.placeholders.icoFile" />
<button id="icon-path-search" data-i18n="ui.button.browse">Browse</button>
</div>
<div>
<span id="icon-invalid-warning" style="display: none">
⚠️
<span data-i18n="ui.notes.invalidIcoFormatWarning">Warning: this file is not a valid .ico file</span>
</span>
</div>
</div>
</div>
<div id="section-additional-files">
<div class="header noselect" onclick="expandSection('additional-files')">
<img src="img/chevron-square-up.svg" alt="Additional Files Section Chevron" />
<h2>
<span data-i18n="ui.title.additionalFiles">Additional Files</span>
<small>(--add-data)</small>
</h2>
</div>
<div class="content">
<div id="datas-add-buttons">
<button id="additional-files-add-files-button" data-i18n="ui.button.addFiles">Add Files</button>
<button id="additional-files-add-folder" data-i18n="ui.button.addFolder">Add Folder</button>
<button id="additional-files-add-blank" data-i18n="ui.button.addBlank">Add Blank</button>
</div>
<div id="datas-list"></div>
<p
id="onefileAdditionalFilesNote"
class="note"
style="display: none"
data-i18n="ui.notes.oneFileAdditionalFilesNote"
>
Be careful when using additional files with onefile mode;
<a href="https://stackoverflow.com/a/13790741/" style="text-decoration: none">read this</a>
and update your code to work with PyInstaller.
</p>
<p class="note" data-i18n="ui.notes.rootDirectory">
If you want to put files in the root directory, put a period (.) in the destination.
</p>
</div>
</div>
<div id="section-advanced">
<div class="header noselect" onclick="expandSection('advanced')">
<img src="img/chevron-square-up.svg" alt="Advanced Section Chevron" />
<h2 data-i18n="ui.title.advanced">Advanced</h2>
</div>
<div class="content"></div>
</div>
<div id="section-settings">
<div class="header noselect" onclick="expandSection('settings')">
<img src="img/chevron-square-up.svg" alt="Advanced Section Chevron" />
<h2 data-i18n="ui.title.settings">Settings</h2>
</div>
<div class="content">
<div>
<h3 data-i18n="ui.title.specificOptions">auto-py-to-exe Specific Options</h3>
<div class="option-container input">
<span>
<span data-i18n="ui.title.outputDirectory">Output Directory</span>
<span
title="The directory to put the output in. Will be created if it doesn't exist"
class="info_icon"
data-i18n_title="ui.helpText.outputDirectory"
></span>
</span>
<div class="filepath-browse-layout">
<input
id="output-directory"
placeholder="DIRECTORY"
data-i18n_placeholder="ui.placeholders.directory"
/>
<button id="output-directory-search" data-i18n="ui.button.browse">Browse</button>
</div>
</div>
<div class="option-container switch">
<span>
<span data-i18n="ui.title.increaseRecursionLimit">Increase Recursion Limit</span>
<span
title="Having this enabled will set the recursion limit to 5000 using sys.setrecursionlimit(5000)."
class="info_icon"
data-i18n_title="ui.helpText.increaseRecursionLimit"
></span>
</span>
<button id="recursion-limit-switch" data-i18n="ui.button.enable">Enable</button>
</div>
</div>
<div>
<h3 data-i18n="ui.title.manuallyProvideOptions">Manually Provide Options</h3>
<div class="option-container input">
<span>
<span data-i18n="ui.title.manualArgumentInput">Manual Argument Input</span>
<span
title="Inject raw text into the generated command."
class="info_icon"
data-i18n_title="ui.helpText.manualArgumentInput"
></span>
</span>
<input id="raw-arguments" placeholder="ARGUMENTS" data-i18n_placeholder="ui.placeholders.arguments" />
</div>
</div>
<div>
<h3 data-i18n="ui.title.configuration">Configuration</h3>
<button id="configuration-import" data-i18n="ui.button.importConfig">Import Config From JSON File</button>
<button id="configuration-export" data-i18n="ui.button.exportConfig">Export Config To JSON File</button>
</div>
</div>
</div>
<div id="current-command">
<h2 data-i18n="ui.title.currentCommand">Current Command</h2>
<textarea readonly></textarea>
</div>
<div id="output">
<h2 data-i18n="ui.title.output">Output</h2>
<textarea readonly></textarea>
</div>
<div id="common-issue-link" data-i18n="ui.notes.somethingWrongWithOutput">
Something wrong with your exe? Read
<a
href="https://nitratine.net/blog/post/issues-when-using-auto-py-to-exe/?utm_source=auto_py_to_exe&utm_medium=application_link&utm_campaign=auto_py_to_exe_help&utm_content=bottom"
target="_blank"
>
this post on how to fix common issues
</a>
for possible solutions.
</div>
<div id="package-button-wrapper">
<button id="package-button" data-i18n="ui.button.convert">Convert .py to .exe</button>
<button id="open-output-folder-button" data-i18n="ui.button.openOutputFolder">Open Output Folder</button>
</div>
</div>
<div id="modal-area" class="modal-coverage modal-coverage-hidden"></div>
<div id="spinner-root" class="loading-spinner-wrapper">
<div>
<div class="loading-spinner"></div>
<span class="loading-label">Initializing...</span>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,107 @@
/*
Handle configuration modifications
*/
const configurationGetters = []; // Each function in this should either return null or [option.dest, value]
const configurationSetters = {}; // dest: fn(value) => void, used to set option values
const configurationCleaners = []; // Each function in this should clear a dest value
// Get option-value pairs [[option, value], ...]
const getCurrentConfiguration = async (skipTransformations = false) => {
const currentConfiguration = [
{
optionDest: 'noconfirm',
value: true,
},
];
// Call all functions to get data
configurationGetters.forEach((getter) => {
const optionValuePair = getter();
if (optionValuePair !== null) {
currentConfiguration.push({
optionDest: optionValuePair[0],
value: optionValuePair[1],
});
}
});
if (skipTransformations) {
return currentConfiguration;
}
// Convert all relative paths to absolute paths
for (const c of currentConfiguration) {
const option = options.find((o) => o.dest === c.optionDest);
if (option === undefined) {
continue;
}
if (
[OPTION_INPUT_VALUE_FILE, OPTION_INPUT_VALUE_DIRECTORY].some((v) => option.allowedInputValues.includes(v)) ||
option.dest === 'filenames'
) {
c.value = await convertPathToAbsolute(c.value);
}
if (
[OPTION_INPUT_VALUE_DOUBLE_FILE_DEST, OPTION_INPUT_VALUE_DOUBLE_DIRECTORY_DEST].some((v) =>
option.allowedInputValues.includes(v)
)
) {
const [src, dest] = c.value.split(pathSeparator);
c.value = `${await convertPathToAbsolute(src)}${pathSeparator}${dest}`;
}
}
return currentConfiguration;
};
const getNonPyinstallerConfiguration = () => {
return {
outputDirectory: document.getElementById('output-directory').value,
increaseRecursionLimit: !document.getElementById('recursion-limit-switch').classList.contains('unselected'),
manualArguments: document.getElementById('raw-arguments').value,
};
};
const getCurrentCommand = async () => {
const currentConfiguration = await getCurrentConfiguration();
// Match configuration values with the correct flags
const optionsAndValues = currentConfiguration
.filter((c) => c.optionDest !== 'filenames')
.map((c) => {
// Identify the options
const option = options.find((o) => o.dest === c.optionDest);
if (option.nargs === 0) {
// For switches, there are some switches for false switches that we can use
const potentialOption = options.find((o) => o.dest === c.optionDest && o.const === c.value);
if (potentialOption !== undefined) {
return chooseOptionString(potentialOption.option_strings);
} else {
return null; // If there is no alternate option, skip it as it won't be required
}
} else {
const optionFlag = chooseOptionString(option.option_strings);
return `${optionFlag} "${c.value}"`;
}
})
.filter((x) => x !== null);
// Identify the entry script provided
const entryScriptConfig = currentConfiguration.find((c) => c.optionDest === 'filenames');
const entryScript = entryScriptConfig === undefined ? '' : entryScriptConfig.value;
return `pyinstaller ${optionsAndValues.join(' ')} ${
getNonPyinstallerConfiguration().manualArguments
} "${entryScript}"`;
};
const updateCurrentCommandDisplay = async () => {
document.querySelector('#current-command textarea').value = await getCurrentCommand();
};
const isCommandDefault = async () => {
return (await getCurrentCommand()) === 'pyinstaller --noconfirm --onedir --console ""';
};

View File

@@ -0,0 +1,84 @@
const options_ignored = ['help'];
const options_static = ['filenames', 'onefile', 'console', 'icon_file', 'datas'];
const options_overridden = ['specpath', 'distpath', 'workpath', 'noconfirm'];
const options_inputTypeFile = [
'runtime_hooks',
'version_file',
'manifest',
'resources',
'splash',
'entitlements_file',
'icon_file',
];
const options_inputTypeDirectory = ['upx_dir', 'pathex', 'hookspath'];
const options_inputTypeDoubleFileDest = ['datas', 'binaries'];
const options_inputTypeDoubleDirectoryDest = ['datas'];
const advancedSections = [
{
titleI18nPath: 'dynamic.title.generalOptions',
options: ['name', 'contents_directory', 'upx_dir', 'clean_build', 'loglevel'],
},
{
titleI18nPath: 'dynamic.title.whatToBundleWhereToSearch',
options: [
'binaries',
'pathex',
'hiddenimports',
'collect_submodules',
'collect_data',
'collect_binaries',
'collect_all',
'copy_metadata',
'recursive_copy_metadata',
'splash',
'hookspath',
'runtime_hooks',
'excludes',
'key',
],
},
{
titleI18nPath: 'dynamic.title.howToGenerate',
options: ['debug', 'optimize', 'python_options', 'strip', 'noupx', 'upx_exclude'],
},
{
titleI18nPath: 'dynamic.title.windowsAndMacOsXSpecificOptions',
options: ['hide_console', 'disable_windowed_traceback'],
},
{
titleI18nPath: 'dynamic.title.windowsSpecificOptions',
options: ['version_file', 'manifest', 'embed_manifest', 'resources', 'uac_admin', 'uac_uiaccess'],
},
{
titleI18nPath: 'dynamic.title.macOsxSpecificOptions',
options: ['argv_emulation', 'bundle_identifier', 'target_arch', 'codesign_identity', 'entitlements_file'],
},
{
titleI18nPath: 'dynamic.title.rarelyUsedSpecialOptions',
options: ['runtime_tmpdir', 'bootloader_ignore_signals'],
},
];
// String constants
OPTION_IGNORED = 'OPTION_IGNORED';
OPTION_STATIC = 'OPTION_STATIC';
OPTION_OVERRIDDEN = 'OPTION_OVERRIDDEN';
OPTION_SHOW = 'OPTION_SHOW';
OPTION_INPUT_TYPE_SWITCH = 'OPTION_INPUT_TYPE_SWITCH';
OPTION_INPUT_TYPE_DROPDOWN = 'OPTION_INPUT_TYPE_DROPDOWN';
OPTION_INPUT_TYPE_INPUT = 'OPTION_INPUT_TYPE_INPUT';
OPTION_INPUT_TYPE_MULTIPLE_INPUT = 'OPTION_INPUT_TYPE_MULTIPLE_INPUT';
OPTION_INPUT_TYPE_DOUBLE_MULTIPLE_INPUT = 'OPTION_INPUT_TYPE_DOUBLE_MULTIPLE_INPUT';
OPTION_INPUT_VALUE_TEXT = 'OPTION_INPUT_VALUE_TEXT';
OPTION_INPUT_VALUE_FILE = 'OPTION_INPUT_VALUE_FILE';
OPTION_INPUT_VALUE_DIRECTORY = 'OPTION_INPUT_VALUE_DIRECTORY';
OPTION_INPUT_VALUE_DOUBLE_FILE_DEST = 'OPTION_INPUT_VALUE_DOUBLE_FILE_DEST';
OPTION_INPUT_VALUE_DOUBLE_DIRECTORY_DEST = 'OPTION_INPUT_VALUE_DOUBLE_DIRECTORY_DEST';
PACKAGING_STATE_READY = 'PACKAGING_STATE_READY';
PACKAGING_STATE_PACKAGING = 'PACKAGING_STATE_PACKAGING';
PACKAGING_STATE_COMPLETE = 'PACKAGING_STATE_COMPLETE';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
const importConfiguration = (configuration) => {
// TODO Check for version to support older versions
// Re-init UI by clearing everything (copy the array first as it will be mutated during the iteration)
[...configurationCleaners].forEach((cleaner) => cleaner());
if ('pyinstallerOptions' in configuration) {
configuration.pyinstallerOptions.forEach(({ optionDest, value }) => {
if (configurationSetters.hasOwnProperty(optionDest)) {
configurationSetters[optionDest](value);
} else {
// TODO Warn user?
// TODO noconfirm is expected to come here
}
});
}
// setup nonPyinstallerOptions
if ('nonPyinstallerOptions' in configuration) {
if ('increaseRecursionLimit' in configuration.nonPyinstallerOptions) {
recursionLimitToggle(configuration.nonPyinstallerOptions.increaseRecursionLimit);
}
if ('manualArguments' in configuration.nonPyinstallerOptions) {
document.getElementById('raw-arguments').value = configuration.nonPyinstallerOptions.manualArguments;
}
if ('outputDirectory' in configuration.nonPyinstallerOptions) {
document.getElementById('output-directory').value = configuration.nonPyinstallerOptions.outputDirectory;
}
}
};
const _collectDataToExport = async () => {
const nonPyinstallerConfiguration = getNonPyinstallerConfiguration();
delete nonPyinstallerConfiguration.outputDirectory; // This does not need to be saved in the config
return {
version: 'auto-py-to-exe-configuration_v1',
pyinstallerOptions: await getCurrentConfiguration(true),
nonPyinstallerOptions: nonPyinstallerConfiguration,
};
};
const onConfigurationImport = async () => {
if (!(await isCommandDefault())) {
const response = await displayModal(
getTranslation('dynamic.modal.configModalTitle'),
getTranslation('dynamic.modal.configModalDescription'),
[
getTranslation('dynamic.modal.configModalConfirmButton'),
getTranslation('dynamic.modal.configModalCancelButton'),
]
);
if (response !== getTranslation('dynamic.modal.configModalConfirmButton')) return;
}
const data = await eel.import_configuration()();
if (data !== null) {
importConfiguration(data);
}
};
const onConfigurationExport = async () => {
const data = await _collectDataToExport();
await eel.export_configuration(data)();
};

View File

@@ -0,0 +1,109 @@
/*
Handle the initialisation of the ui
*/
let options = [];
let pathSeparator = '';
const buildUpOptions = (providedOptions) => {
return providedOptions.map((option) => {
const name = option.dest;
let placement = OPTION_SHOW;
if (options_ignored.indexOf(name) !== -1) {
placement = OPTION_IGNORED;
} else if (options_static.indexOf(name) !== -1) {
placement = OPTION_STATIC;
} else if (options_overridden.indexOf(name) !== -1) {
placement = OPTION_OVERRIDDEN;
}
let inputType = OPTION_INPUT_TYPE_INPUT;
if (option.nargs === 0) {
inputType = OPTION_INPUT_TYPE_SWITCH;
} else if (option.choices !== null) {
inputType = OPTION_INPUT_TYPE_DROPDOWN;
} else if (option.dest === 'datas' || option.dest === 'binaries') {
inputType = OPTION_INPUT_TYPE_DOUBLE_MULTIPLE_INPUT;
} else if (option.default !== null || option.dest === 'upx_exclude') {
inputType = OPTION_INPUT_TYPE_MULTIPLE_INPUT;
}
const allowedInputValues = [];
if (options_inputTypeFile.indexOf(name) !== -1) {
allowedInputValues.push(OPTION_INPUT_VALUE_FILE);
}
if (options_inputTypeDirectory.indexOf(name) !== -1) {
allowedInputValues.push(OPTION_INPUT_VALUE_DIRECTORY);
}
if (options_inputTypeDoubleFileDest.indexOf(name) !== -1) {
allowedInputValues.push(OPTION_INPUT_VALUE_DOUBLE_FILE_DEST);
}
if (options_inputTypeDoubleDirectoryDest.indexOf(name) !== -1) {
allowedInputValues.push(OPTION_INPUT_VALUE_DOUBLE_DIRECTORY_DEST);
}
if (allowedInputValues.length === 0) {
allowedInputValues.push(OPTION_INPUT_VALUE_TEXT);
}
return {
...option,
placement,
inputType,
allowedInputValues,
};
});
};
// Get initialisation data from the server and setup the ui
window.addEventListener('load', async () => {
// Get initialisation data from Python
console.log('Getting initialisation data');
const initialisationData = await eel.initialise()();
console.log('Received initialisation data');
options = buildUpOptions(initialisationData.options);
pathSeparator = initialisationData.pathSeparator;
// Setup user's default color scheme
setupTheme();
// Setup ui events (for static content) and setup initial state
setupEvents();
// Setup language selection
setupLanguageSelection();
// Setup advanced section (for dynamic content)
constructAdvancedSection(options);
// Setup json config file is supplied
if (initialisationData.suppliedUiConfiguration !== null) {
importConfiguration(initialisationData.suppliedUiConfiguration);
}
// Set the output directory to the default if it hasn't already been set by `initialisationData.suppliedUiConfiguration`
if (document.getElementById('output-directory').value === '') {
document.getElementById('output-directory').value = initialisationData.defaultOutputFolder;
}
// If a file is provided, put it in the script location
if (initialisationData.filename !== null) {
configurationSetters['filenames'](initialisationData.filename);
}
// Display any warnings provided
setupWarnings(initialisationData.warnings);
// Update the current command when setup is complete
await updateCurrentCommandDisplay();
// Try to translate to the default browser language
translate(initialisationData.languageHint);
// If the server stops, close the UI
window.eel._websocket.addEventListener('close', (e) => window.close());
console.log('Application initialised');
document.getElementById('spinner-root').style.display = 'none';
});

View File

@@ -0,0 +1,462 @@
/*
Handle visual events
*/
// Expand a section (typically triggered by clicking on a section heading)
const expandSection = (sectionName) => {
const root = document.getElementById(`section-${sectionName}`);
const chevron = root.querySelector('.header img');
const content = root.querySelector(`.content`);
if (root.getAttribute('data-expanded') === null) {
// Show the section
chevron.style.transform = 'rotate(0deg)';
content.style.display = 'block';
root.setAttribute('data-expanded', '');
} else {
// Hide the section
chevron.style.transform = 'rotate(180deg)';
content.style.display = 'none';
root.removeAttribute('data-expanded');
}
};
// Colour an input based on the "allowed" arguments. Returns whether the field is valid or not
const colourInput = async (inputNode, allowedToBeEmpty, allowedToBeFile, allowedToBeADirectory) => {
const { value } = inputNode;
if (
(allowedToBeEmpty && value === '') ||
(!allowedToBeEmpty && value !== '' && !allowedToBeFile && !allowedToBeADirectory) ||
(allowedToBeFile && (await doesFileExist(value))) ||
(allowedToBeADirectory && (await doesFolderExist(value)))
) {
inputNode.style.border = '';
return true;
} else {
inputNode.style.border = '1px solid rgb(244, 67, 54)';
return false;
}
};
const addDoubleInputForSrcDst = (
parentNode,
optionDest,
source,
destination,
sourceCanBeFile,
sourceCanBeDirectory
) => {
// Construct visible inputs
const wrapper = document.createElement('div');
parentNode.appendChild(wrapper);
const sourceInput = document.createElement('input');
wrapper.appendChild(sourceInput);
const destinationInput = document.createElement('input');
wrapper.appendChild(destinationInput);
const removeButton = document.createElement('img');
wrapper.appendChild(removeButton);
wrapper.classList.add('dual-value');
sourceInput.value = source;
sourceInput.addEventListener('input', (event) => {
colourInput(sourceInput, false, sourceCanBeFile, sourceCanBeDirectory);
void updateCurrentCommandDisplay();
});
colourInput(sourceInput, false, sourceCanBeFile, sourceCanBeDirectory);
destinationInput.value = destination;
destinationInput.addEventListener('input', (event) => {
void updateCurrentCommandDisplay();
});
// Add configurationGetter
const configurationGetter = () => [optionDest, `${sourceInput.value}${pathSeparator}${destinationInput.value}`];
configurationGetters.push(configurationGetter);
// Setup removal
const onRemove = () => {
wrapper.remove();
const configurationGetterIndex = configurationGetters.indexOf(configurationGetter);
configurationGetters.splice(configurationGetterIndex, 1);
const configurationCleanerIndex = configurationCleaners.indexOf(onRemove);
configurationCleaners.splice(configurationCleanerIndex, 1);
void updateCurrentCommandDisplay();
};
removeButton.src = 'img/remove.svg';
removeButton.addEventListener('click', onRemove);
configurationCleaners.push(onRemove);
void updateCurrentCommandDisplay();
};
const _createSubSectionInAdvanced = (title, i18nPath, options) => {
const parent = document.querySelector('#section-advanced .content');
// The div wrapping the whole section
const subSectionNode = document.createElement('div');
parent.appendChild(subSectionNode);
// Setup title
const subSectionTitleNode = document.createElement('h3');
subSectionTitleNode.textContent = title;
subSectionTitleNode.classList.add('noselect');
subSectionTitleNode.dataset.i18n = i18nPath;
subSectionNode.appendChild(subSectionTitleNode);
// Setup options
options.forEach((o) => {
// Container for option
const container = document.createElement('div');
subSectionNode.appendChild(container);
container.classList.add('option-container');
// Option title / name
const optionNode = document.createElement('span');
container.appendChild(optionNode);
optionNode.textContent = chooseOptionString(o.option_strings);
// Help icon
const helpNode = document.createElement('span');
optionNode.appendChild(helpNode); // Put the icon inside the option text
helpNode.title = o.help.replace(/R\|/, '');
helpNode.classList.add('info_icon');
if (o.inputType === OPTION_INPUT_TYPE_SWITCH) {
container.classList.add('switch');
// Add button (take note of the target argument state using `const`)
const enableButton = document.createElement('button');
container.appendChild(enableButton);
if (o.const === true) {
enableButton.dataset.i18n = 'dynamic.button.enable';
} else if (o.const === false) {
enableButton.dataset.i18n = 'dynamic.button.disable';
} else {
throw new Error('Unknown o.const value: ' + JSON.stringify(o));
}
enableButton.textContent = getTranslation(enableButton.dataset.i18n);
enableButton.classList.add('unselected');
// Function used to set the value of the switch
const setValue = (enabled) => {
if (enabled) {
enableButton.classList.remove('unselected');
enableButton.classList.add('selected');
} else {
enableButton.classList.add('unselected');
enableButton.classList.remove('selected');
}
void updateCurrentCommandDisplay();
};
// When clicked, toggle the value
enableButton.addEventListener('click', () => {
setValue(!enableButton.classList.contains('selected'));
});
// Add configurationGetter
const configurationGetter = () => [o.dest, !enableButton.classList.contains('unselected')];
configurationGetters.push(configurationGetter);
// Add configurationSetter
configurationSetters[o.dest] = setValue;
// Add configurationCleaner
configurationCleaners.push(() => setValue(false));
// Allow a default value of `true` to come through
if (o.default === true) {
setValue(true);
}
} else if (o.inputType === OPTION_INPUT_TYPE_DROPDOWN) {
container.classList.add('choice');
// Add dropdown
const selectNode = document.createElement('select');
container.appendChild(selectNode);
selectNode.addEventListener('change', (event) => {
void updateCurrentCommandDisplay();
});
// Add options (including default '')
const defaultOptionNode = document.createElement('option');
selectNode.appendChild(defaultOptionNode);
defaultOptionNode.textContent = '';
const choices = Array.isArray(o.choices) ? o.choices : Object.keys(o.choices);
choices.map((choice) => {
const optionNode = document.createElement('option');
selectNode.appendChild(optionNode);
optionNode.textContent = choice;
optionNode.value = choice;
});
// Add configurationGetter
const configurationGetter = () => {
const value = selectNode.value;
return value === '' ? null : [o.dest, value];
};
configurationGetters.push(configurationGetter);
// Add configurationSetter
configurationSetters[o.dest] = (value) => {
if (choices.indexOf(value) !== 1) {
selectNode.value = value;
} else {
selectNode.value = '';
}
selectNode.dispatchEvent(new Event('change'));
};
// Add configurationCleaner
configurationCleaners.push(() => {
selectNode.value = '';
selectNode.dispatchEvent(new Event('change'));
});
} else if (o.inputType === OPTION_INPUT_TYPE_INPUT) {
container.classList.add('input');
const isOptionFileBased = o.allowedInputValues.indexOf(OPTION_INPUT_VALUE_FILE) !== -1;
const isOptionDirectoryBased = o.allowedInputValues.indexOf(OPTION_INPUT_VALUE_DIRECTORY) !== -1;
// Add input node
const inputNode = document.createElement('input');
container.appendChild(inputNode);
inputNode.placeholder = o.metavar || 'VALUE';
inputNode.addEventListener('input', (event) => {
void updateCurrentCommandDisplay();
if (isOptionFileBased || isOptionDirectoryBased) {
colourInput(inputNode, true, isOptionFileBased, isOptionDirectoryBased);
}
});
// Show browse button if required (only file or folder - not both)
if (isOptionFileBased || isOptionDirectoryBased) {
container.classList.add('with-browse');
const searchButton = document.createElement('button');
container.appendChild(searchButton);
searchButton.dataset.i18n = isOptionFileBased
? 'dynamic.button.browseForFile'
: 'dynamic.button.browseForFolder';
searchButton.textContent = getTranslation(searchButton.dataset.i18n);
searchButton.addEventListener('click', async () => {
const value = isOptionFileBased ? await askForFile(null) : await askForFolder();
if (value !== null) {
inputNode.value = value;
inputNode.dispatchEvent(new Event('input'));
}
});
}
// Add configurationGetter
const configurationGetter = () => {
const value = inputNode.value;
return value === '' ? null : [o.dest, value];
};
configurationGetters.push(configurationGetter);
// Add configurationSetter
configurationSetters[o.dest] = (value) => {
inputNode.value = value;
inputNode.dispatchEvent(new Event('input'));
};
// Add configurationCleaner
configurationCleaners.push(() => {
inputNode.value = '';
inputNode.dispatchEvent(new Event('input'));
});
} else if (o.inputType === OPTION_INPUT_TYPE_MULTIPLE_INPUT) {
container.classList.add('multiple-input');
const isOptionFileBased = o.allowedInputValues.indexOf(OPTION_INPUT_VALUE_FILE) !== -1;
const isOptionDirectoryBased = o.allowedInputValues.indexOf(OPTION_INPUT_VALUE_DIRECTORY) !== -1;
// Add button to add an entry
const addButton = document.createElement('img');
container.appendChild(addButton);
addButton.src = 'img/plus.svg';
// Container to hold the values
const valuesContainer = document.createElement('div');
container.appendChild(valuesContainer);
const addValue = (value) => {
// Container to hold the pair
const valueContainer = document.createElement('div');
valuesContainer.appendChild(valueContainer);
// Value input
const inputNode = document.createElement('input');
valueContainer.appendChild(inputNode);
inputNode.value = value;
inputNode.placeholder = o.metavar || 'VALUE';
colourInput(inputNode, false, isOptionFileBased, isOptionDirectoryBased);
inputNode.addEventListener('input', (event) => {
colourInput(inputNode, false, isOptionFileBased, isOptionDirectoryBased);
void updateCurrentCommandDisplay();
});
// Add configurationGetter
const configurationGetter = () => [o.dest, inputNode.value];
configurationGetters.push(configurationGetter);
// Remove button
const removeButtonNode = document.createElement('img');
removeButtonNode.src = 'img/remove.svg';
valueContainer.appendChild(removeButtonNode);
const onRemove = () => {
valueContainer.remove();
const configurationGetterIndex = configurationGetters.indexOf(configurationGetter);
configurationGetters.splice(configurationGetterIndex, 1);
const configurationCleanerIndex = configurationCleaners.indexOf(onRemove);
configurationCleaners.splice(configurationCleanerIndex, 1);
void updateCurrentCommandDisplay();
};
removeButtonNode.addEventListener('click', onRemove);
// Add configurationCleaner
configurationCleaners.push(onRemove);
void updateCurrentCommandDisplay();
};
// Event to add a new input pair
addButton.addEventListener('click', async () => {
// Get initial value
let initialValue = '';
if (isOptionFileBased || isOptionDirectoryBased) {
initialValue = isOptionFileBased ? await askForFile(null) : await askForFolder();
if (initialValue === null) {
return;
}
}
addValue(initialValue);
});
// Add configurationSetter
configurationSetters[o.dest] = (value) => {
addValue(value);
};
} else if (o.inputType === OPTION_INPUT_TYPE_DOUBLE_MULTIPLE_INPUT) {
container.classList.add('multiple-input');
const isOptionFileBased = o.allowedInputValues.indexOf(OPTION_INPUT_VALUE_DOUBLE_FILE_DEST) !== -1;
const isOptionDirectoryBased = o.allowedInputValues.indexOf(OPTION_INPUT_VALUE_DOUBLE_DIRECTORY_DEST) !== -1;
// Add button to add an entry
const addButton = document.createElement('img');
container.appendChild(addButton);
addButton.src = 'img/plus.svg';
// Container to hold the value pairs
const valuesContainer = document.createElement('div');
container.appendChild(valuesContainer);
addButton.addEventListener('click', async () => {
// Get initial value
let initialValue = '';
if (isOptionFileBased || isOptionDirectoryBased) {
initialValue = isOptionFileBased ? await askForFile(null) : await askForFolder();
if (initialValue === null) {
return;
}
}
addDoubleInputForSrcDst(valuesContainer, o.dest, initialValue, '.', true, false);
});
// Add configurationSetter
configurationSetters[o.dest] = (value) => {
const [val1, val2] = value.split(pathSeparator);
addDoubleInputForSrcDst(valuesContainer, o.dest, val1, val2, true, false);
};
}
});
};
const constructAdvancedSection = () => {
// Setup pre-defined sections
advancedSections.forEach((section) =>
_createSubSectionInAdvanced(
getTranslation(section.titleI18nPath),
section.titleI18nPath,
options.filter((o) => section.options.indexOf(o.dest) !== -1)
)
);
// Setup extra arguments
const usedSectionOptions = flatMap(advancedSections.map((s) => s.options));
const extraOptions = options.filter(
(option) =>
usedSectionOptions.indexOf(option.dest) === -1 &&
option.placement !== OPTION_IGNORED &&
option.placement !== OPTION_STATIC &&
option.placement !== OPTION_OVERRIDDEN
);
if (extraOptions.length > 0) {
_createSubSectionInAdvanced(getTranslation('dynamic.title.other'), 'dynamic.title.other', extraOptions);
}
};
const setupWarnings = (warnings) => {
if (warnings.length === 0) {
return;
}
const warningsRootNode = document.getElementById('warnings');
warnings.forEach((warningContent) => {
// Create wrapper
const wrapperNode = document.createElement('div');
warningsRootNode.appendChild(wrapperNode);
// Create message
const messageNode = document.createElement('p');
wrapperNode.appendChild(messageNode);
messageNode.innerHTML = warningContent;
});
};
const setupLanguageSelection = () => {
const languageSelectNode = document.getElementById('language-selection');
languageSelectNode.addEventListener('change', (event) => {
translate(event.target.value);
});
supportedLanguages.forEach((language) => {
const option = document.createElement('option');
option.innerText = language.name;
option.value = language.code;
languageSelectNode.appendChild(option);
});
languageSelectNode.value = currentLanguage;
};
// Toggle theme (triggered by clicking moon or sun)
const _toggleTheme = () => {
const root = document.querySelector('body');
const onDarkThemeButton = document.querySelector('#on-dark-theme-button');
const onLightThemeButton = document.querySelector('#on-light-theme-button');
if (root.classList.contains('dark-theme')) {
onLightThemeButton.style.display = 'inline';
onDarkThemeButton.style.display = 'none';
} else {
// dark
onLightThemeButton.style.display = 'none';
onDarkThemeButton.style.display = 'inline';
}
root.classList.toggle('dark-theme');
};
// Check if user's default color scheme is dark
const setupTheme = () => {
document.getElementById('theme-toggle').addEventListener('click', _toggleTheme);
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
_toggleTheme();
}
};

View File

@@ -0,0 +1,19 @@
eel.expose(putMessageInOutput);
function putMessageInOutput(message) {
const outputNode = document.querySelector('#output textarea');
outputNode.value += message; // Add the message
if (!message.endsWith('\n')) {
outputNode.value += '\n'; // If there was no new line, add one
}
// Set the correct height to fit all the output and then scroll to the bottom
outputNode.style.height = 'auto';
outputNode.style.height = outputNode.scrollHeight + 10 + 'px';
window.scrollTo(0, document.body.scrollHeight);
}
eel.expose(signalPackagingComplete);
function signalPackagingComplete(successful) {
setPackagingComplete(successful);
window.scrollTo(0, document.body.scrollHeight);
}

View File

@@ -0,0 +1,105 @@
/*
* Renders the native JS modal over the window.
* Returns selected option from **buttonOptions** list.
*
* Input:
* - title: string
* - description: string
* - [optional] buttonOptions: string[] = ['Yes', 'No']
* - [optional]: closeEvent: string = 'Close'
*
* Returns:
* - Promise<string>
*/
const displayModal = (title, description, buttonOptions = ['Yes', 'No'], closeEvent = 'Close') => {
const buildHeader = (_title) => {
const header = document.createElement('div');
header.classList.add('modal-section', 'modal-header');
const closeButton = document.createElement('span');
closeButton.classList.add('close-btn');
closeButton.innerHTML = '&times;';
header.appendChild(closeButton);
const titleElement = document.createElement('h2');
titleElement.innerText = _title;
header.appendChild(titleElement);
return {
header: header,
closeButton: closeButton,
};
};
const buildBody = (_description) => {
const modalBody = document.createElement('div');
modalBody.classList.add('modal-section');
const descriptionElement = document.createElement('a');
descriptionElement.innerText = _description;
modalBody.appendChild(descriptionElement);
return {
body: modalBody,
};
};
const buildFooter = () => {
const footerButtons = [];
const footer = document.createElement('div');
footer.classList.add('modal-section', 'modal-footer');
for (const label of buttonOptions) {
const footerButton = document.createElement('button');
footerButton.classList.add('modal-btn');
footerButton.innerText = label;
footer.appendChild(footerButton);
footerButtons.push(footerButton);
}
return {
footer: footer,
footerButtons: footerButtons,
};
};
const modalArea = document.getElementById('modal-area');
modalArea.classList.remove('modal-coverage-hidden');
const headerElement = buildHeader(title);
const bodyElement = buildBody(description);
const footerElement = buildFooter();
const footerButtons = footerElement.footerButtons;
const clearEventListeners = () => {
headerElement.closeButton.removeEventListener('click', (_) => {});
footerButtons.forEach((button) => button.removeEventListener('click', (_) => {}));
};
const modalContent = document.createElement('div');
modalContent.classList.add('modal-content');
modalContent.appendChild(headerElement.header);
modalContent.appendChild(bodyElement.body);
modalContent.appendChild(footerElement.footer);
modalArea.appendChild(modalContent);
return new Promise((resolve) => {
headerElement.closeButton.addEventListener('click', (_) => {
clearEventListeners();
modalArea.removeChild(modalContent);
modalArea.classList.add('modal-coverage-hidden');
resolve(closeEvent);
});
for (const [label, button] of zip(buttonOptions, footerButtons)) {
button.addEventListener('click', (_) => {
clearEventListeners();
modalArea.removeChild(modalContent);
modalArea.classList.add('modal-coverage-hidden');
resolve(label);
});
}
});
};

View File

@@ -0,0 +1,61 @@
let packagingState = PACKAGING_STATE_READY;
const setPackagingState = (newState) => {
packagingState = newState;
const outputSectionNode = document.getElementById('output');
const outputTextAreaNode = outputSectionNode.querySelector('textarea');
const convertButtonNode = document.getElementById('package-button');
const openOutputButtonNode = document.getElementById('open-output-folder-button');
const commonIssueLinkNode = document.getElementById('common-issue-link');
switch (newState) {
case PACKAGING_STATE_READY:
// Clear output
outputSectionNode.classList.remove('show');
outputTextAreaNode.value = '';
outputTextAreaNode.rows = 0;
outputTextAreaNode.classList.remove('failure');
// Set the main button back to initial value
convertButtonNode.dataset.i18n = 'ui.button.convert';
convertButtonNode.innerHTML = getTranslation(convertButtonNode.dataset.i18n);
// Hide open folder button
openOutputButtonNode.classList.remove('show');
// Hide common issue link
commonIssueLinkNode.classList.remove('show');
return;
case PACKAGING_STATE_PACKAGING:
// Disable convert button
convertButtonNode.disabled = true;
convertButtonNode.dataset.i18n = 'dynamic.button.converting';
convertButtonNode.innerHTML = getTranslation(convertButtonNode.dataset.i18n);
// Show output
outputSectionNode.classList.add('show');
return;
case PACKAGING_STATE_COMPLETE:
// Re-enable convert button and re-purpose it
convertButtonNode.disabled = false;
convertButtonNode.dataset.i18n = 'dynamic.button.clearOutput';
convertButtonNode.innerHTML = getTranslation(convertButtonNode.dataset.i18n);
// Show open folder button (beside "Clear Output" button)
openOutputButtonNode.classList.add('show');
// Show common issue link
commonIssueLinkNode.classList.add('show');
return;
}
};
const startPackaging = async () => {
eel.package(await getCurrentCommand(), getNonPyinstallerConfiguration())();
setPackagingState(PACKAGING_STATE_PACKAGING);
};
const setPackagingComplete = (successful) => {
setPackagingState(PACKAGING_STATE_COMPLETE);
// Show red border around output on failure
if (!successful) {
const outputTextAreaNode = document.querySelector('#output textarea');
outputTextAreaNode.classList.add('failure');
}
};

View File

@@ -0,0 +1,261 @@
/*
Handle user events
*/
// Top level inputs
const scriptLocationChange = async (event) => {
colourInput(event.target, false, true, false);
void updateCurrentCommandDisplay();
};
const scriptLocationSearch = async (event) => {
const entryScriptNode = document.getElementById('entry-script');
const value = await askForFile('python');
if (value !== null) {
entryScriptNode.value = value;
await scriptLocationChange({ target: entryScriptNode });
}
};
const oneFileOptionChange = (option) => (event) => {
const onefileAdditionalFilesNote = document.getElementById('onefileAdditionalFilesNote');
onefileAdditionalFilesNote.style.display = option === 'one-file' ? 'block' : 'none'; // Show the note if one-file is being used
const oneFileButton = document.getElementById('one-file-button');
oneFileButton.classList.add(option === 'one-file' ? 'selected' : 'unselected');
oneFileButton.classList.remove(option !== 'one-file' ? 'selected' : 'unselected');
const oneDirectoryButton = document.getElementById('one-directory-button');
oneDirectoryButton.classList.add(option === 'one-directory' ? 'selected' : 'unselected');
oneDirectoryButton.classList.remove(option !== 'one-directory' ? 'selected' : 'unselected');
void updateCurrentCommandDisplay();
};
const consoleWindowOptionChange = (option) => (event) => {
const consoleButton = document.getElementById('console-based-button');
consoleButton.classList.add(option === 'console' ? 'selected' : 'unselected');
consoleButton.classList.remove(option !== 'console' ? 'selected' : 'unselected');
const windowButton = document.getElementById('window-based-button');
windowButton.classList.add(option === 'window' ? 'selected' : 'unselected');
windowButton.classList.remove(option !== 'window' ? 'selected' : 'unselected');
void updateCurrentCommandDisplay();
};
const iconLocationChange = async (event) => {
const valid = await colourInput(event.target, true, true, false);
void updateCurrentCommandDisplay();
// If valid and a value exists, show the message if the file is not an ico file
const warningElement = document.getElementById('icon-invalid-warning');
if (valid && event.target.value !== '') {
const isIcoFile = await isFileAnIco(event.target.value);
warningElement.style.display = isIcoFile === false ? 'block' : 'none'; // isIcoFile is boolean | null
} else {
warningElement.style.display = 'none';
}
};
const iconLocationSearch = async (event) => {
const iconPathNode = document.getElementById('icon-path');
const value = await askForFile('icon');
if (value !== null) {
iconPathNode.value = value;
await iconLocationChange({ target: iconPathNode });
}
};
const additionalFilesAddFiles = async (event) => {
const files = await askForFiles();
if (files !== null) {
const datasListNode = document.getElementById('datas-list');
files.forEach((file) => {
addDoubleInputForSrcDst(datasListNode, 'datas', file, '.', true, true);
});
}
};
const additionalFilesAddFolder = async (event) => {
const folder = await askForFolder();
if (folder !== '') {
const datasListNode = document.getElementById('datas-list');
const destinationFolder = folder.split(/[/\\]/);
addDoubleInputForSrcDst(
datasListNode,
'datas',
folder,
`${destinationFolder[destinationFolder.length - 1]}/`,
true,
true
);
}
};
const additionalFilesAddBlank = (event) => {
const datasListNode = document.getElementById('datas-list');
addDoubleInputForSrcDst(datasListNode, 'datas', '', '.', true, true);
};
// Settings section events
const outputDirectorySearch = async (event) => {
const folder = await askForFolder();
if (folder !== '') {
const outputDirectoryInput = document.getElementById('output-directory');
outputDirectoryInput.value = folder;
}
};
const recursionLimitToggle = (enabled) => {
const button = document.getElementById('recursion-limit-switch');
if (enabled) {
button.classList.add('selected');
button.classList.remove('unselected');
} else {
button.classList.remove('selected');
button.classList.add('unselected');
}
};
const rawArgumentsChange = (event) => {
void updateCurrentCommandDisplay();
};
const packageScript = async (event) => {
if (packagingState === PACKAGING_STATE_PACKAGING) {
// Do not do anything while packaging
return;
}
if (packagingState === PACKAGING_STATE_COMPLETE) {
// This is now the clear output button
setPackagingState(PACKAGING_STATE_READY);
return;
}
// Pre-checks
const currentConfiguration = await getCurrentConfiguration();
const entryScript = currentConfiguration.find((c) => c.optionDest === 'filenames').value;
if (entryScript === '') {
alert(getTranslation('nonDom.alert.noScriptsLocationProvided'));
return;
}
const willOverwrite = await eel.will_packaging_overwrite_existing(
entryScript,
currentConfiguration.find((c) => c.optionDest === 'name')?.value,
currentConfiguration.find((c) => c.optionDest === 'onefile').value,
getNonPyinstallerConfiguration().outputDirectory
)();
if (willOverwrite && !confirm(getTranslation('nonDom.alert.overwritePreviousOutput'))) {
return;
}
// If checks have passed, package the script
await startPackaging();
};
const openOutputFolder = async (event) => {
const currentConfiguration = await getCurrentConfiguration();
const entryScript = currentConfiguration.find((c) => c.optionDest === 'filenames').value;
const isOneFile = currentConfiguration.find((c) => c.optionDest === 'onefile').value;
eel.open_output_in_explorer(getNonPyinstallerConfiguration().outputDirectory, entryScript, isOneFile)();
};
const setupEvents = () => {
// Script location
document.getElementById('entry-script').addEventListener('input', scriptLocationChange);
document.getElementById('entry-script-search').addEventListener('click', scriptLocationSearch);
// Output bundle type
document.getElementById('one-directory-button').addEventListener('click', oneFileOptionChange('one-directory'));
document.getElementById('one-file-button').addEventListener('click', oneFileOptionChange('one-file'));
// Console switch
document.getElementById('console-based-button').addEventListener('click', consoleWindowOptionChange('console'));
document.getElementById('window-based-button').addEventListener('click', consoleWindowOptionChange('window'));
// Icon
document.getElementById('icon-path').addEventListener('input', iconLocationChange);
document.getElementById('icon-path-search').addEventListener('click', iconLocationSearch);
// Additional files
document.getElementById('additional-files-add-files-button').addEventListener('click', additionalFilesAddFiles);
document.getElementById('additional-files-add-folder').addEventListener('click', additionalFilesAddFolder);
document.getElementById('additional-files-add-blank').addEventListener('click', additionalFilesAddBlank);
// Settings
document.getElementById('output-directory-search').addEventListener('click', outputDirectorySearch);
document
.getElementById('recursion-limit-switch')
.addEventListener('click', (e) => recursionLimitToggle(e.target.classList.contains('unselected')));
document.getElementById('raw-arguments').addEventListener('input', rawArgumentsChange);
document.getElementById('configuration-import').addEventListener('click', () => onConfigurationImport());
document.getElementById('configuration-export').addEventListener('click', () => onConfigurationExport());
// Build buttons
document.getElementById('package-button').addEventListener('click', packageScript);
document.getElementById('open-output-folder-button').addEventListener('click', openOutputFolder);
// Add configurationGetters
const getEntryScript = () => ['filenames', document.getElementById('entry-script').value];
const getOnefile = () => [
'onefile',
document.getElementById('one-directory-button').classList.contains('unselected'),
];
const getConsole = () => ['console', document.getElementById('window-based-button').classList.contains('unselected')];
const getIcon = () => {
const path = document.getElementById('icon-path').value;
return path === '' ? null : ['icon_file', path];
};
configurationGetters.push(getEntryScript);
configurationGetters.push(getOnefile);
configurationGetters.push(getConsole);
configurationGetters.push(getIcon);
// Add configurationSetters
const setEntryScript = (value) => {
document.getElementById('entry-script').value = value;
scriptLocationChange({ target: document.getElementById('entry-script') });
};
const setOnefile = (value) => {
if (value) {
document.getElementById('one-directory-button').classList.add('unselected');
document.getElementById('one-file-button').classList.remove('unselected');
} else {
document.getElementById('one-directory-button').classList.remove('unselected');
document.getElementById('one-file-button').classList.add('unselected');
}
};
const setConsole = (value) => {
if (value) {
document.getElementById('console-based-button').classList.remove('unselected');
document.getElementById('window-based-button').classList.add('unselected');
} else {
document.getElementById('console-based-button').classList.add('unselected');
document.getElementById('window-based-button').classList.remove('unselected');
}
};
const setAdditionalFile = (value) => {
const datasListNode = document.getElementById('datas-list');
const [val1, val2] = value.split(pathSeparator);
addDoubleInputForSrcDst(datasListNode, 'datas', val1, val2, true, true);
};
const setIcon = (value) => {
document.getElementById('icon-path').value = value;
document.getElementById('icon-path').dispatchEvent(new Event('input'));
};
configurationSetters['filenames'] = setEntryScript;
configurationSetters['onefile'] = setOnefile;
configurationSetters['console'] = setConsole;
configurationSetters['datas'] = setAdditionalFile;
configurationSetters['icon_file'] = setIcon;
configurationCleaners.push(() => setEntryScript('')); // filenames
configurationCleaners.push(() => setOnefile(false)); // onefile
configurationCleaners.push(() => setConsole(true)); // console
configurationCleaners.push(() => setIcon('')); // icon_file
// Soft initialise (to trigger any required initial events)
setEntryScript('');
setOnefile(false);
setConsole(true);
};

View File

@@ -0,0 +1,50 @@
/*
Util functions
*/
const flatMap = (xs) => xs.reduce((x, y) => x.concat(y), []); // Not all browsers have Array.flatMap
/*
* Equivalent of Python zip(*args) function. Usage:
*
* for (let [var1, ..., varN] of zip(arr1, ..., arrN)) {
* ...
* }
*/
const zip = (...arrays) => [...arrays[0]].map((_, index) => arrays.map((arr) => arr[index]));
const doesFileExist = async (path) => {
return await eel.does_file_exist(path)();
};
const doesFolderExist = async (path) => {
return await eel.does_folder_exist(path)();
};
const askForFile = async (fileType) => {
return await eel.ask_file(fileType)();
};
const askForFiles = async () => {
return await eel.ask_files()();
};
const askForFolder = async () => {
return await eel.ask_folder()();
};
const isFileAnIco = async (file_path) => {
return await eel.is_file_an_ico(file_path)();
};
const convertPathToAbsolute = async (path) => {
return await eel.convert_path_to_absolute(path)();
};
const chooseOptionString = (optionStrings) => {
// Try not to use compressed flags
if (optionStrings[0].length === 2 && optionStrings.length > 1) {
return optionStrings[1];
}
return optionStrings[0];
};