diff --git a/.gitignore b/.gitignore
index b45d20d..5d7a226 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,4 +34,6 @@ npm-debug.log
output.png
*.pyc
-uploads/*
\ No newline at end of file
+uploads/*
+
+dev.secret.exs
\ No newline at end of file
diff --git a/assets/css/app.css b/assets/css/app.css
index 19c2e51..b8425f6 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -1,6 +1,11 @@
-/* This file is for your main application CSS */
-@import "./phoenix.css";
+@import "tailwindcss/base";
+@import "tailwindcss/components";
+@import "tailwindcss/utilities";
+/* This file is for your main application CSS */
+.bg-gray {
+ background-color: rgb(20, 20, 20);
+}
/* Alerts and form errors used by phx.new */
.alert {
padding: 15px;
@@ -46,12 +51,12 @@
transition: opacity 1s ease-out;
}
-.phx-loading{
+.phx-loading {
cursor: wait;
}
.phx-modal {
- opacity: 1!important;
+ opacity: 1 !important;
position: fixed;
z-index: 1;
left: 0;
@@ -59,15 +64,36 @@
width: 100%;
height: 100%;
overflow: auto;
- background-color: rgba(0,0,0,0.4);
+ background-color: rgba(0, 0, 0, 0.4);
}
.phx-modal-content {
- background-color: #fefefe;
+ background-color: #323232;
margin: 15vh auto;
- padding: 20px;
+ padding: 20\px;
border: 1px solid #888;
- width: 80%;
+ max-width: 600px;
+}
+
+.modal-label {
+ display: inline-block;
+ min-width: 140px;
+ text-align: right;
+}
+
+.pagination-link {
+ height: 45px;
+ line-height: 45px;
+ width: 175px;
+}
+
+.pagination-btns {
+ width: 420px;
+ max-width: 100%;
+}
+
+.center-placeholder::placeholder {
+ text-align: center;
}
.phx-modal-close {
@@ -99,22 +125,42 @@
animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys;
}
-@keyframes fade-in-scale-keys{
- 0% { scale: 0.95; opacity: 0; }
- 100% { scale: 1.0; opacity: 1; }
+@keyframes fade-in-scale-keys {
+ 0% {
+ scale: 0.95;
+ opacity: 0;
+ }
+ 100% {
+ scale: 1;
+ opacity: 1;
+ }
}
-@keyframes fade-out-scale-keys{
- 0% { scale: 1.0; opacity: 1; }
- 100% { scale: 0.95; opacity: 0; }
+@keyframes fade-out-scale-keys {
+ 0% {
+ scale: 1;
+ opacity: 1;
+ }
+ 100% {
+ scale: 0.95;
+ opacity: 0;
+ }
}
-@keyframes fade-in-keys{
- 0% { opacity: 0; }
- 100% { opacity: 1; }
+@keyframes fade-in-keys {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
}
-@keyframes fade-out-keys{
- 0% { opacity: 1; }
- 100% { opacity: 0; }
+@keyframes fade-out-keys {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
}
diff --git a/assets/css/phoenix.css b/assets/css/phoenix.css
deleted file mode 100644
index 0d59050..0000000
--- a/assets/css/phoenix.css
+++ /dev/null
@@ -1,101 +0,0 @@
-/* Includes some default style for the starter application.
- * This can be safely deleted to start fresh.
- */
-
-/* Milligram v1.4.1 https://milligram.github.io
- * Copyright (c) 2020 CJ Patoilo Licensed under the MIT license
- */
-
-*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#000000;font-family:'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#0069d9;border:0.1rem solid #0069d9;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#0069d9;border-color:#0069d9}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#0069d9}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#0069d9}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#0069d9}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#0069d9}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #0069d9;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='color'],input[type='date'],input[type='datetime'],input[type='datetime-local'],input[type='email'],input[type='month'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],input[type='week'],input:not([type]),textarea,select{-webkit-appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem .7rem;width:100%}input[type='color']:focus,input[type='date']:focus,input[type='datetime']:focus,input[type='datetime-local']:focus,input[type='email']:focus,input[type='month']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,input[type='week']:focus,input:not([type]):focus,textarea:focus,select:focus{border-color:#0069d9;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}select[multiple]{background:none;height:auto}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-40{margin-left:40%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-60{margin-left:60%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#0069d9;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;display:block;overflow-x:auto;text-align:left;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}@media (min-width: 40rem){table{display:table;overflow-x:initial}}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right}
-
-/* General style */
-h1{font-size: 3.6rem; line-height: 1.25}
-h2{font-size: 2.8rem; line-height: 1.3}
-h3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35}
-h4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5}
-h5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4}
-h6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2}
-pre{padding: 1em;}
-
-.container{
- margin: 0 auto;
- max-width: 80.0rem;
- padding: 0 2.0rem;
- position: relative;
- width: 100%
-}
-select {
- width: auto;
-}
-
-/* Phoenix promo and logo */
-.phx-hero {
- text-align: center;
- border-bottom: 1px solid #e3e3e3;
- background: #eee;
- border-radius: 6px;
- padding: 3em 3em 1em;
- margin-bottom: 3rem;
- font-weight: 200;
- font-size: 120%;
-}
-.phx-hero input {
- background: #ffffff;
-}
-.phx-logo {
- min-width: 300px;
- margin: 1rem;
- display: block;
-}
-.phx-logo img {
- width: auto;
- display: block;
-}
-
-/* Headers */
-header {
- width: 100%;
- background: #fdfdfd;
- border-bottom: 1px solid #eaeaea;
- margin-bottom: 2rem;
-}
-header section {
- align-items: center;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-}
-header section :first-child {
- order: 2;
-}
-header section :last-child {
- order: 1;
-}
-header nav ul,
-header nav li {
- margin: 0;
- padding: 0;
- display: block;
- text-align: right;
- white-space: nowrap;
-}
-header nav ul {
- margin: 1rem;
- margin-top: 0;
-}
-header nav a {
- display: block;
-}
-
-@media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */
- header section {
- flex-direction: row;
- }
- header nav ul {
- margin: 1rem;
- }
- .phx-logo {
- flex-basis: 527px;
- margin: 2rem 1rem;
- }
-}
diff --git a/assets/js/app.js b/assets/js/app.js
index 34cde6f..12df8b8 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -1,6 +1,5 @@
// We import the CSS which is extracted to its own file by esbuild.
// Remove this line if you add a your own CSS build pipeline (e.g postcss).
-import "../css/app.css"
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
diff --git a/assets/package-lock.json b/assets/package-lock.json
new file mode 100644
index 0000000..e3d5622
--- /dev/null
+++ b/assets/package-lock.json
@@ -0,0 +1,1551 @@
+{
+ "name": "assets",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "daisyui": "^2.24.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-node": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
+ "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+ "dependencies": {
+ "acorn": "^7.0.0",
+ "acorn-walk": "^7.0.0",
+ "xtend": "^4.0.2"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
+ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.8",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.8.tgz",
+ "integrity": "sha512-75Jr6Q/XpTqEf6D2ltS5uMewJIx5irCU1oBYJrWjFenq/m12WRRrz6g15L1EIoYvPLXTbEry7rDOwrcYNj77xw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ }
+ ],
+ "peer": true,
+ "dependencies": {
+ "browserslist": "^4.21.3",
+ "caniuse-lite": "^1.0.30001373",
+ "fraction.js": "^4.2.0",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.0",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.21.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz",
+ "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "peer": true,
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001370",
+ "electron-to-chromium": "^1.4.202",
+ "node-releases": "^2.0.6",
+ "update-browserslist-db": "^1.0.5"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001387",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001387.tgz",
+ "integrity": "sha512-fKDH0F1KOJvR+mWSOvhj8lVRr/Q/mc5u5nabU2vi1/sgvlSqEsE8dOq0Hy/BqVbDkCYQPRRHB1WRjW6PGB/7PA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ }
+ ],
+ "peer": true
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/css-selector-tokenizer": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
+ "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "fastparse": "^1.1.2"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/daisyui": {
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.24.0.tgz",
+ "integrity": "sha512-Fdu/4LCdTfWLWAbCuPxvnaRotEfJ+hVPgZ2kv/aUk9RZ00Yk8fGdJtIf0kXJ3IgUKOr8rCXUpfQY6DQU9usPCQ==",
+ "dependencies": {
+ "color": "^4.2",
+ "css-selector-tokenizer": "^0.8.0",
+ "postcss-js": "^4.0.0",
+ "tailwindcss": "^3"
+ },
+ "peerDependencies": {
+ "autoprefixer": "^10.0.2",
+ "postcss": "^8.1.6"
+ }
+ },
+ "node_modules/defined": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+ "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ=="
+ },
+ "node_modules/detective": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz",
+ "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==",
+ "dependencies": {
+ "acorn-node": "^1.8.2",
+ "defined": "^1.0.0",
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "detective": "bin/detective.js"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.237",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.237.tgz",
+ "integrity": "sha512-vxVyGJcsgArNOVUJcXm+7iY3PJAfmSapEszQD1HbyPLl0qoCmNQ1o/EX3RI7Et5/88In9oLxX3SGF8J3orkUgA==",
+ "peer": true
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.11",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
+ "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fastparse": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
+ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
+ },
+ "node_modules/fastq": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
+ "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
+ "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
+ "peer": true,
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://www.patreon.com/infusion"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz",
+ "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==",
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
+ "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
+ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
+ "peer": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.16",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
+ "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz",
+ "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz",
+ "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.3.3"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+ "dependencies": {
+ "lilconfig": "^2.0.5",
+ "yaml": "^1.10.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
+ "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.6"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.8.tgz",
+ "integrity": "sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==",
+ "dependencies": {
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "color-name": "^1.1.4",
+ "detective": "^5.2.1",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.2.11",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "lilconfig": "^2.0.6",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.14",
+ "postcss-import": "^14.1.0",
+ "postcss-js": "^4.0.0",
+ "postcss-load-config": "^3.1.4",
+ "postcss-nested": "5.0.6",
+ "postcss-selector-parser": "^6.0.10",
+ "postcss-value-parser": "^4.2.0",
+ "quick-lru": "^5.1.1",
+ "resolve": "^1.22.1"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.9"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz",
+ "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "peer": true,
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "browserslist-lint": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "engines": {
+ "node": ">= 6"
+ }
+ }
+ },
+ "dependencies": {
+ "@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "requires": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ }
+ },
+ "@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
+ },
+ "@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "requires": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ }
+ },
+ "acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
+ },
+ "acorn-node": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
+ "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+ "requires": {
+ "acorn": "^7.0.0",
+ "acorn-walk": "^7.0.0",
+ "xtend": "^4.0.2"
+ }
+ },
+ "acorn-walk": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
+ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
+ },
+ "anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
+ },
+ "autoprefixer": {
+ "version": "10.4.8",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.8.tgz",
+ "integrity": "sha512-75Jr6Q/XpTqEf6D2ltS5uMewJIx5irCU1oBYJrWjFenq/m12WRRrz6g15L1EIoYvPLXTbEry7rDOwrcYNj77xw==",
+ "peer": true,
+ "requires": {
+ "browserslist": "^4.21.3",
+ "caniuse-lite": "^1.0.30001373",
+ "fraction.js": "^4.2.0",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.0",
+ "postcss-value-parser": "^4.2.0"
+ }
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "browserslist": {
+ "version": "4.21.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz",
+ "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==",
+ "peer": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001370",
+ "electron-to-chromium": "^1.4.202",
+ "node-releases": "^2.0.6",
+ "update-browserslist-db": "^1.0.5"
+ }
+ },
+ "camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
+ },
+ "caniuse-lite": {
+ "version": "1.0.30001387",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001387.tgz",
+ "integrity": "sha512-fKDH0F1KOJvR+mWSOvhj8lVRr/Q/mc5u5nabU2vi1/sgvlSqEsE8dOq0Hy/BqVbDkCYQPRRHB1WRjW6PGB/7PA==",
+ "peer": true
+ },
+ "chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "requires": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ }
+ }
+ },
+ "color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "requires": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "requires": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "css-selector-tokenizer": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
+ "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
+ "requires": {
+ "cssesc": "^3.0.0",
+ "fastparse": "^1.1.2"
+ }
+ },
+ "cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
+ },
+ "daisyui": {
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.24.0.tgz",
+ "integrity": "sha512-Fdu/4LCdTfWLWAbCuPxvnaRotEfJ+hVPgZ2kv/aUk9RZ00Yk8fGdJtIf0kXJ3IgUKOr8rCXUpfQY6DQU9usPCQ==",
+ "requires": {
+ "color": "^4.2",
+ "css-selector-tokenizer": "^0.8.0",
+ "postcss-js": "^4.0.0",
+ "tailwindcss": "^3"
+ }
+ },
+ "defined": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+ "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ=="
+ },
+ "detective": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz",
+ "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==",
+ "requires": {
+ "acorn-node": "^1.8.2",
+ "defined": "^1.0.0",
+ "minimist": "^1.2.6"
+ }
+ },
+ "didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
+ },
+ "dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
+ },
+ "electron-to-chromium": {
+ "version": "1.4.237",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.237.tgz",
+ "integrity": "sha512-vxVyGJcsgArNOVUJcXm+7iY3PJAfmSapEszQD1HbyPLl0qoCmNQ1o/EX3RI7Et5/88In9oLxX3SGF8J3orkUgA==",
+ "peer": true
+ },
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "peer": true
+ },
+ "fast-glob": {
+ "version": "3.2.11",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
+ "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+ "requires": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ }
+ }
+ },
+ "fastparse": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
+ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
+ },
+ "fastq": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
+ "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+ "requires": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "fraction.js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
+ "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
+ "peer": true
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-core-module": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz",
+ "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==",
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
+ },
+ "is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+ },
+ "lilconfig": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
+ "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg=="
+ },
+ "merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
+ },
+ "micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "requires": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ }
+ },
+ "minimist": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
+ },
+ "nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
+ },
+ "node-releases": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
+ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
+ "peer": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
+ },
+ "normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "peer": true
+ },
+ "object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="
+ },
+ "path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
+ },
+ "postcss": {
+ "version": "8.4.16",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
+ "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
+ "requires": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "postcss-import": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz",
+ "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==",
+ "requires": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ }
+ },
+ "postcss-js": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz",
+ "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==",
+ "requires": {
+ "camelcase-css": "^2.0.1"
+ }
+ },
+ "postcss-load-config": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+ "requires": {
+ "lilconfig": "^2.0.5",
+ "yaml": "^1.10.2"
+ }
+ },
+ "postcss-nested": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
+ "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
+ "requires": {
+ "postcss-selector-parser": "^6.0.6"
+ }
+ },
+ "postcss-selector-parser": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "requires": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
+ "queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
+ },
+ "quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
+ },
+ "read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "requires": {
+ "pify": "^2.3.0"
+ }
+ },
+ "readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "requires": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
+ },
+ "run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "requires": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "requires": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
+ },
+ "supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
+ },
+ "tailwindcss": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.8.tgz",
+ "integrity": "sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==",
+ "requires": {
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "color-name": "^1.1.4",
+ "detective": "^5.2.1",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.2.11",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "lilconfig": "^2.0.6",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.14",
+ "postcss-import": "^14.1.0",
+ "postcss-js": "^4.0.0",
+ "postcss-load-config": "^3.1.4",
+ "postcss-nested": "5.0.6",
+ "postcss-selector-parser": "^6.0.10",
+ "postcss-value-parser": "^4.2.0",
+ "quick-lru": "^5.1.1",
+ "resolve": "^1.22.1"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "update-browserslist-db": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz",
+ "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==",
+ "peer": true,
+ "requires": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+ },
+ "yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
+ }
+ }
+}
diff --git a/assets/package.json b/assets/package.json
new file mode 100644
index 0000000..7c09b1d
--- /dev/null
+++ b/assets/package.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "daisyui": "^2.24.0"
+ }
+}
diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js
new file mode 100644
index 0000000..9a59235
--- /dev/null
+++ b/assets/tailwind.config.js
@@ -0,0 +1,30 @@
+// See the Tailwind configuration guide for advanced usage
+// https://tailwindcss.com/docs/configuration
+
+let plugin = require('tailwindcss/plugin')
+
+module.exports = {
+ content: [
+ './js/**/*.js',
+ '../lib/*_web.ex',
+ '../lib/*_web/**/*.*ex'
+ ],
+ theme: {
+ extend: {},
+ container: {
+ center: true,
+ },
+ },
+ daisyui: {
+ themes: ["black"],
+ },
+ plugins: [
+ require("@tailwindcss/typography"),
+ require('@tailwindcss/forms'),
+ plugin(({addVariant}) => addVariant('phx-no-feedback', ['&.phx-no-feedback', '.phx-no-feedback &'])),
+ plugin(({addVariant}) => addVariant('phx-click-loading', ['&.phx-click-loading', '.phx-click-loading &'])),
+ plugin(({addVariant}) => addVariant('phx-submit-loading', ['&.phx-submit-loading', '.phx-submit-loading &'])),
+ plugin(({addVariant}) => addVariant('phx-change-loading', ['&.phx-change-loading', '.phx-change-loading &'])),
+ require('daisyui')
+ ]
+}
diff --git a/config/config.exs b/config/config.exs
index 1be349d..b645f1f 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -10,9 +10,11 @@ import Config
config :diffuser,
ecto_repos: [Diffuser.Repo]
+config :diffuser, Diffuser.Repo, migration_primary_key: [name: :id, type: :binary_id]
+
# Configures the endpoint
config :diffuser, DiffuserWeb.Endpoint,
- url: [host: "https://ai.silentsilas.com"],
+ url: [host: "127.0.0.1", port: 4000],
render_errors: [view: DiffuserWeb.ErrorView, accepts: ~w(html json), layout: false],
pubsub_server: Diffuser.PubSub,
check_origin: false,
@@ -52,7 +54,18 @@ config :phoenix, :json_library, Jason
config :waffle,
storage: Waffle.Storage.Local,
# or {:system, "ASSET_HOST"}
- asset_host: "https://ai.silentsilas.com"
+ asset_host: "http://gen.silentsilas.com"
+
+config :tailwind,
+ version: "3.1.6",
+ default: [
+ args: ~w(
+ --config=tailwind.config.js
+ --input=css/app.css
+ --output=../priv/static/assets/app.css
+ ),
+ cd: Path.expand("../assets", __DIR__)
+ ]
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/config/dev.exs b/config/dev.exs
index 8008638..489aff1 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -2,6 +2,7 @@ import Config
# Configure your database
config :diffuser, Diffuser.Repo,
+ migration_primary_key: [name: :id, type: :binary_id],
username: "postgres",
password: "postgres",
hostname: "localhost",
@@ -26,7 +27,8 @@ config :diffuser, DiffuserWeb.Endpoint,
secret_key_base: "mLAQpOCHwqL2jI5ljtcULJFRTpsXnvFSjLRM3HjFYJF8Rc1Uqh6o7PT7GaIQ+ERA",
watchers: [
# Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
- esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
+ esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
+ tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
]
# ## SSL Support
@@ -73,3 +75,7 @@ config :phoenix, :stacktrace_depth, 20
# Initialize plugs at runtime for faster development compilation
config :phoenix, :plug_init_mode, :runtime
+
+if File.exists?("config/dev.secret.exs") do
+ import_config "dev.secret.exs"
+end
diff --git a/lib/diffuser/accounts.ex b/lib/diffuser/accounts.ex
new file mode 100644
index 0000000..c207a0d
--- /dev/null
+++ b/lib/diffuser/accounts.ex
@@ -0,0 +1,205 @@
+defmodule Diffuser.Accounts do
+ @moduledoc """
+ The Accounts context.
+ """
+
+ import Ecto.Query, warn: false
+ alias Diffuser.Repo
+
+ alias Diffuser.Accounts.User
+ alias Diffuser.Generator.PromptRequest
+
+ @doc """
+ Returns the list of users.
+
+ ## Examples
+
+ iex> list_users()
+ [%User{}, ...]
+
+ """
+ def list_users do
+ Repo.all(User)
+ end
+
+ @doc """
+ Gets a single user.
+
+ Raises `Ecto.NoResultsError` if the User does not exist.
+
+ ## Examples
+
+ iex> get_user!(123)
+ %User{}
+
+ iex> get_user!(456)
+ ** (Ecto.NoResultsError)
+
+ """
+ def get_user!(id), do: Repo.get!(User, id) |> Repo.preload(votes: [:prompt_request])
+
+ @doc """
+ Creates a user.
+
+ ## Examples
+
+ iex> create_user(%{field: value})
+ {:ok, %User{}}
+
+ iex> create_user(%{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def create_user(attrs \\ %{}) do
+ %User{}
+ |> User.changeset(attrs)
+ |> Repo.insert()
+ end
+
+ @doc """
+ Updates a user.
+
+ ## Examples
+
+ iex> update_user(user, %{field: new_value})
+ {:ok, %User{}}
+
+ iex> update_user(user, %{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def update_user(%User{} = user, attrs) do
+ user
+ |> User.changeset(attrs)
+ |> Repo.update()
+ end
+
+ @doc """
+ Deletes a user.
+
+ ## Examples
+
+ iex> delete_user(user)
+ {:ok, %User{}}
+
+ iex> delete_user(user)
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def delete_user(%User{} = user) do
+ Repo.delete(user)
+ end
+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking user changes.
+
+ ## Examples
+
+ iex> change_user(user)
+ %Ecto.Changeset{data: %User{}}
+
+ """
+ def change_user(%User{} = user, attrs \\ %{}) do
+ User.changeset(user, attrs)
+ end
+
+ alias Diffuser.Accounts.Vote
+
+ @doc """
+ Returns the list of votes.
+
+ ## Examples
+
+ iex> list_votes()
+ [%Vote{}, ...]
+
+ """
+ def list_votes do
+ Repo.all(Vote)
+ end
+
+ @doc """
+ Gets a single vote.
+
+ Raises `Ecto.NoResultsError` if the Vote does not exist.
+
+ ## Examples
+
+ iex> get_vote!(123)
+ %Vote{}
+
+ iex> get_vote!(456)
+ ** (Ecto.NoResultsError)
+
+ """
+ def get_vote!(id), do: Repo.get!(Vote, id)
+
+ @doc """
+ Creates a vote.
+
+ ## Examples
+
+ iex> create_vote(%{field: value})
+ {:ok, %Vote{}}
+
+ iex> create_vote(%{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def create_vote(%{user: user} = attrs) do
+ %Vote{}
+ |> Vote.changeset(attrs)
+ |> Ecto.Changeset.put_assoc(:user, user)
+ |> Repo.insert()
+ end
+
+ @doc """
+ Updates a vote.
+
+ ## Examples
+
+ iex> update_vote(vote, %{field: new_value})
+ {:ok, %Vote{}}
+
+ iex> update_vote(vote, %{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def update_vote(%Vote{} = vote, attrs) do
+ vote
+ |> Vote.changeset(attrs)
+ |> Repo.update()
+ end
+
+ @doc """
+ Deletes a vote.
+
+ ## Examples
+
+ iex> delete_vote(vote)
+ {:ok, %Vote{}}
+
+ iex> delete_vote(vote)
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def delete_vote(%Vote{} = vote) do
+ Repo.delete(vote)
+ end
+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking vote changes.
+
+ ## Examples
+
+ iex> change_vote(vote)
+ %Ecto.Changeset{data: %Vote{}}
+
+ """
+ def change_vote(%Vote{} = vote, attrs \\ %{}) do
+ Vote.changeset(vote, attrs)
+ end
+
+ def upvote(user, %PromptRequest{id: pr}),
+ do: create_vote(%{prompt_request_id: pr, user: user})
+end
diff --git a/lib/diffuser/accounts/user.ex b/lib/diffuser/accounts/user.ex
new file mode 100644
index 0000000..4c2311a
--- /dev/null
+++ b/lib/diffuser/accounts/user.ex
@@ -0,0 +1,25 @@
+defmodule Diffuser.Accounts.User do
+ use Diffuser.Schema
+ import Ecto.Changeset
+ alias Diffuser.Accounts.Vote
+ alias Diffuser.Generator.PromptRequest
+
+ schema "users" do
+ field :ip_address, :string
+ field :username, :string
+ field :code, :string
+
+ has_many :votes, Vote, on_delete: :delete_all
+ has_many :prompt_requests, PromptRequest, on_delete: :delete_all
+
+ timestamps()
+ end
+
+ @doc false
+ def changeset(user, attrs) do
+ user
+ |> cast(attrs, [:ip_address, :username, :code])
+ |> cast_assoc(:votes)
+ |> cast_assoc(:prompt_requests)
+ end
+end
diff --git a/lib/diffuser/accounts/vote.ex b/lib/diffuser/accounts/vote.ex
new file mode 100644
index 0000000..e29d893
--- /dev/null
+++ b/lib/diffuser/accounts/vote.ex
@@ -0,0 +1,20 @@
+defmodule Diffuser.Accounts.Vote do
+ use Diffuser.Schema
+ import Ecto.Changeset
+ alias Diffuser.Generator.PromptRequest
+ alias Diffuser.Accounts.User
+
+ schema "votes" do
+ belongs_to :prompt_request, PromptRequest
+ belongs_to :user, User
+
+ timestamps()
+ end
+
+ @doc false
+ def changeset(vote, attrs) do
+ vote
+ |> cast(attrs, [:prompt_request_id, :user_id])
+ |> validate_required([])
+ end
+end
diff --git a/lib/diffuser/generator.ex b/lib/diffuser/generator.ex
index 16d0589..6dc20f2 100644
--- a/lib/diffuser/generator.ex
+++ b/lib/diffuser/generator.ex
@@ -36,7 +36,9 @@ defmodule Diffuser.Generator do
"""
def get_prompt_request!(id),
- do: Repo.one!(from pr in PromptRequest, where: pr.id == ^id) |> Repo.preload(:images)
+ do:
+ Repo.one!(from pr in PromptRequest, where: pr.id == ^id)
+ |> Repo.preload([:votes, :images, :user])
@doc """
Creates a prompt_request.
@@ -50,9 +52,18 @@ defmodule Diffuser.Generator do
{:error, %Ecto.Changeset{}}
"""
- def create_prompt_request(attrs \\ %{}) do
+ def create_prompt_request(%{"user" => user} = attrs) do
+ user = Diffuser.Accounts.get_user!(user)
+ code = Map.get(user, :code, nil)
+
+ attrs =
+ if is_nil(code),
+ do: attrs |> Map.put("code", "garbage"),
+ else: attrs |> Map.put("code", code)
+
%PromptRequest{}
|> PromptRequest.changeset(attrs)
+ |> Ecto.Changeset.put_assoc(:user, user)
|> Repo.insert()
end
@@ -74,6 +85,9 @@ defmodule Diffuser.Generator do
|> Repo.update()
end
+ def update_prompt_request(id, attrs),
+ do: get_prompt_request!(id) |> update_prompt_request(attrs)
+
@doc """
Deletes a prompt_request.
@@ -123,9 +137,14 @@ defmodule Diffuser.Generator do
end)
end
- def paginate_prompt_requests(params) do
- PromptRequest
- |> preload(:images)
+ def paginate_prompt_requests(query \\ PromptRequest, params) do
+ search_query = Map.get(params, :query, nil)
+ order_by_query = Map.get(params, :order_by, nil)
+
+ query
+ |> PromptRequest.search_by_prompt_or_user(search_query)
+ |> PromptRequest.order_by(order_by_query)
+ |> preload([:votes, :user, :images])
|> Repo.paginate(params)
end
end
diff --git a/lib/diffuser/generator/prompt_request.ex b/lib/diffuser/generator/prompt_request.ex
index eb429c7..5906af3 100644
--- a/lib/diffuser/generator/prompt_request.ex
+++ b/lib/diffuser/generator/prompt_request.ex
@@ -1,17 +1,26 @@
defmodule Diffuser.Generator.PromptRequest do
- use Ecto.Schema
+ use Diffuser.Schema
use Waffle.Ecto.Schema
import Ecto.Changeset
+ import Ecto.Query
alias Diffuser.Generator.PromptRequestResult
+ alias Diffuser.Accounts.{User, Vote}
+
+ @valid_codes Application.get_env(:diffuser, :valid_codes, [])
- @primary_key {:id, Ecto.UUID, autogenerate: true}
schema "prompt_requests" do
field :prompt, :string
field :status, :string, default: "queued"
- field :steps, :integer
- field :guidance_scale, :float
+ field :steps, :integer, default: 32
+ field :completed_steps, :integer, default: 0
+ field :guidance_scale, :float, default: 7.5
+ field :began_at, :utc_datetime
+ field :ended_at, :utc_datetime
+ field :code, :string
has_many :images, PromptRequestResult, on_delete: :delete_all
+ has_many :votes, Vote, on_delete: :delete_all
+ belongs_to :user, User
timestamps()
end
@@ -19,7 +28,63 @@ defmodule Diffuser.Generator.PromptRequest do
@doc false
def changeset(prompt_request, attrs) do
prompt_request
- |> cast(attrs, [:prompt, :status, :steps, :guidance_scale])
+ |> cast(attrs, [
+ :prompt,
+ :status,
+ :steps,
+ :guidance_scale,
+ :completed_steps,
+ :began_at,
+ :ended_at,
+ :code
+ ])
+ |> validate_number(:steps, less_than_or_equal_to: 60)
+ |> validate_code()
|> validate_required([:prompt])
end
+
+ defp validate_code(changeset) do
+ validate_change(changeset, :code, fn :code, code ->
+ cond do
+ @valid_codes
+ |> Enum.any?(fn {_name, valid_code} ->
+ valid_code == code
+ end) ->
+ []
+
+ true ->
+ [code: "Code is invalid."]
+ end
+ end)
+ end
+
+ def search_by_prompt_or_user(query, nil), do: query
+
+ def search_by_prompt_or_user(query, search_query) do
+ from p in query,
+ left_join: u in User,
+ on: u.id == p.user_id,
+ where: ilike(p.prompt, ^"%#{search_query}%")
+ end
+
+ def order_by(query, nil) do
+ from p in query,
+ order_by: [desc: p.inserted_at]
+ end
+
+ def order_by(query, "desc") do
+ from p in query,
+ join: v in Vote,
+ on: v.prompt_request_id == p.id,
+ group_by: p.id,
+ order_by: [desc: count(v.id)]
+ end
+
+ def order_by(query, "asc") do
+ from p in query,
+ left_join: v in Vote,
+ on: v.prompt_request_id == p.id,
+ group_by: p.id,
+ order_by: [asc: count(v.id)]
+ end
end
diff --git a/lib/diffuser/generator/prompt_request_result.ex b/lib/diffuser/generator/prompt_request_result.ex
index 24d49b6..6e2ede7 100644
--- a/lib/diffuser/generator/prompt_request_result.ex
+++ b/lib/diffuser/generator/prompt_request_result.ex
@@ -1,13 +1,12 @@
defmodule Diffuser.Generator.PromptRequestResult do
- use Ecto.Schema
+ use Diffuser.Schema
use Waffle.Ecto.Schema
import Ecto.Changeset
alias Diffuser.Generator.PromptRequest
- @primary_key {:id, Ecto.UUID, autogenerate: true}
schema "prompt_request_results" do
field :image, Diffuser.Uploaders.Image.Type
- belongs_to :prompt_request, PromptRequest, type: :binary_id
+ belongs_to :prompt_request, PromptRequest
timestamps()
end
diff --git a/lib/diffuser/generator/prompt_request_worker.ex b/lib/diffuser/generator/prompt_request_worker.ex
index 9e16039..05226ff 100644
--- a/lib/diffuser/generator/prompt_request_worker.ex
+++ b/lib/diffuser/generator/prompt_request_worker.ex
@@ -5,8 +5,6 @@ defmodule Diffuser.Generator.PromptRequestWorker do
alias Diffuser.Repo
@path [:code.priv_dir(:diffuser), "python"] |> Path.join()
- @steps 10
- @guidance_scale 7.5
def start(%PromptRequest{} = prompt_request) do
with {:ok, active_prompt} <-
@@ -26,24 +24,38 @@ defmodule Diffuser.Generator.PromptRequestWorker do
end
end
- defp update_and_broadcast_progress(%PromptRequest{id: id} = prompt_request, new_status) do
- {:ok, new_prompt} =
- Generator.update_prompt_request(prompt_request, %{
- status: new_status,
- steps: @steps,
- guidance_scale: @guidance_scale
+ defp update_and_broadcast_progress(prompt_request, "in_progress"),
+ do:
+ update_and_broadcast_progress(prompt_request, %{
+ status: "in_progress",
+ began_at: NaiveDateTime.utc_now()
})
+ defp update_and_broadcast_progress(prompt_request, "finished"),
+ do:
+ update_and_broadcast_progress(prompt_request, %{
+ status: "finished",
+ ended_at: NaiveDateTime.utc_now()
+ })
+
+ defp update_and_broadcast_progress(%PromptRequest{id: id} = prompt_request, attrs) do
+ {:ok, new_prompt} = Generator.update_prompt_request(prompt_request, attrs)
+
:ok = Endpoint.broadcast("request:#{id}", "request", %{prompt_request: new_prompt})
{:ok, new_prompt}
end
- defp call_python(_module, _func, %{id: prompt_id, prompt: prompt}) do
+ defp call_python(_module, _func, %PromptRequest{
+ id: prompt_id,
+ prompt: prompt,
+ steps: steps,
+ guidance_scale: guidance_scale
+ }) do
port =
Port.open(
{:spawn,
- ~s(python #{@path}/stable_diffusion.py --prompt "#{prompt}" --output "#{@path}/#{prompt_id}.png" --num-inference-steps #{@steps})},
+ ~s(python #{@path}/stable_diffusion.py --prompt "#{prompt}" --output "#{@path}/#{prompt_id}.png" --num-inference-steps #{steps} --guidance-scale #{guidance_scale})},
[:binary]
)
@@ -56,11 +68,18 @@ defmodule Diffuser.Generator.PromptRequestWorker do
{:ok, msg}
{^port, {:data, ":step" <> step}} ->
- Endpoint.broadcast("request:#{prompt_id}", "progress", step)
+ {:ok, prompt_request} =
+ Generator.update_prompt_request(prompt_id, %{completed_steps: step})
+
+ Endpoint.broadcast(
+ "request:#{prompt_id}",
+ "progress",
+ %{prompt_request: prompt_request |> Repo.preload(:images)}
+ )
+
python_loop(port, prompt_id)
- {^port, result} ->
- IO.inspect(result, label: "RESULT")
+ {^port, _result} ->
python_loop(port, prompt_id)
end
end
diff --git a/lib/diffuser/repo.ex b/lib/diffuser/repo.ex
index 016d1ae..758f626 100644
--- a/lib/diffuser/repo.ex
+++ b/lib/diffuser/repo.ex
@@ -3,5 +3,5 @@ defmodule Diffuser.Repo do
otp_app: :diffuser,
adapter: Ecto.Adapters.Postgres
- use Scrivener, page_size: 1
+ use Scrivener, page_size: 12
end
diff --git a/lib/diffuser/schema.ex b/lib/diffuser/schema.ex
new file mode 100644
index 0000000..1cc7db4
--- /dev/null
+++ b/lib/diffuser/schema.ex
@@ -0,0 +1,9 @@
+defmodule Diffuser.Schema do
+ defmacro __using__(_) do
+ quote do
+ use Ecto.Schema
+ @primary_key {:id, :binary_id, autogenerate: true}
+ @foreign_key_type :binary_id
+ end
+ end
+end
diff --git a/lib/diffuser_web/channels/request_channel.ex b/lib/diffuser_web/channels/request_channel.ex
index 70ab5a2..94f6d12 100644
--- a/lib/diffuser_web/channels/request_channel.ex
+++ b/lib/diffuser_web/channels/request_channel.ex
@@ -1,8 +1,7 @@
defmodule DiffuserWeb.RequestChannel do
use Phoenix.Channel
- def join("request:" <> room_id, _message, socket) do
- IO.inspect(room_id)
+ def join("request:" <> _room_id, _message, socket) do
{:ok, socket}
end
end
diff --git a/lib/diffuser_web/live/prompt_request_live/form_component.ex b/lib/diffuser_web/live/prompt_request_live/form_component.ex
index b022a15..0cce648 100644
--- a/lib/diffuser_web/live/prompt_request_live/form_component.ex
+++ b/lib/diffuser_web/live/prompt_request_live/form_component.ex
@@ -2,7 +2,6 @@ defmodule DiffuserWeb.PromptRequestLive.FormComponent do
use DiffuserWeb, :live_component
alias Diffuser.Generator
- alias Diffuser.Generator.PromptRequest
@impl true
def update(%{prompt_request: prompt_request} = assigns, socket) do
@@ -28,30 +27,23 @@ defmodule DiffuserWeb.PromptRequestLive.FormComponent do
save_prompt_request(socket, socket.assigns.action, prompt_request_params)
end
- defp save_prompt_request(socket, :edit, prompt_request_params) do
- case Generator.update_prompt_request(socket.assigns.prompt_request, prompt_request_params) do
- {:ok, %PromptRequest{} = prompt_request} ->
- {:noreply,
- socket
- |> put_flash(:info, "Prompt request updated successfully")
- |> push_redirect(to: Routes.prompt_request_show_path(socket, :show, prompt_request))}
-
- {:error, %Ecto.Changeset{} = changeset} ->
- {:noreply, assign(socket, :changeset, changeset)}
- end
- end
-
- defp save_prompt_request(socket, :new, prompt_request_params) do
+ defp save_prompt_request(%{assigns: %{user: _user}} = socket, :new, prompt_request_params) do
with {:ok, prompt_request} <- Generator.create_prompt_request(prompt_request_params) do
Diffuser.Generator.PromptRequestQueue.enqueue(prompt_request)
{:noreply,
socket
|> put_flash(:info, "Prompt request created successfully")
- |> push_redirect(to: Routes.prompt_request_show_path(socket, :show, prompt_request))}
+ |> push_redirect(to: Routes.prompt_request_index_path(socket, :index))}
else
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
+
+ defp save_prompt_request(socket, :new, _) do
+ {:noreply,
+ socket
+ |> push_redirect(to: Routes.prompt_request_index_path(socket, :index))}
+ end
end
diff --git a/lib/diffuser_web/live/prompt_request_live/form_component.html.heex b/lib/diffuser_web/live/prompt_request_live/form_component.html.heex
index e96c4bc..d7e14ec 100644
--- a/lib/diffuser_web/live/prompt_request_live/form_component.html.heex
+++ b/lib/diffuser_web/live/prompt_request_live/form_component.html.heex
@@ -1,20 +1,41 @@
-
-
<%= @title %>
+
+
+ <.form
+ let={f}
+ for={@changeset}
+ id="prompt_request-form"
+ phx-target={@myself}
+ phx-change="validate"
+ phx-submit="save">
- <.form
- let={f}
- for={@changeset}
- id="prompt_request-form"
- phx-target={@myself}
- phx-change="validate"
- phx-submit="save">
-
- <%= label f, :prompt %>
- <%= text_input f, :prompt %>
- <%= error_tag f, :prompt %>
-
-
- <%= submit "Save", phx_disable_with: "Saving..." %>
-
-
+
+
+ <%= text_input f, :prompt, class: "input" %>
+ <%= error_tag f, :prompt %>
+
+
+
+
+ <%= number_input f, :steps, class: "input", min: 1, max: 60, step: 1 %>
+ <%= error_tag f, :prompt %>
+
+
+
+
+ <%= number_input f, :guidance_scale, class: "input", step: 0.1 %>
+ <%= error_tag f, :prompt %>
+
+
+ <%= hidden_input f, :user, value: @user.id %>
+
+
+ <%= submit "Save", phx_disable_with: "Saving...", class: "btn" %>
+
+
diff --git a/lib/diffuser_web/live/prompt_request_live/index.ex b/lib/diffuser_web/live/prompt_request_live/index.ex
index dff7afa..8535f06 100644
--- a/lib/diffuser_web/live/prompt_request_live/index.ex
+++ b/lib/diffuser_web/live/prompt_request_live/index.ex
@@ -3,46 +3,204 @@ defmodule DiffuserWeb.PromptRequestLive.Index do
alias Diffuser.Generator
alias Diffuser.Generator.PromptRequest
+ alias Diffuser.Accounts
+ alias Diffuser.Accounts.User
+ alias DiffuserWeb.Endpoint
+ alias Phoenix.Socket.Broadcast
+ alias Diffuser.Repo
+ alias Ecto.Association.NotLoaded
@impl true
- def mount(_params, _session, socket) do
- {:ok, socket}
+ def mount(params, %{"user" => user, "is_admin" => is_admin}, socket) do
+ changeset = Accounts.change_user(user)
+
+ {:ok,
+ socket
+ |> assign(:user, user)
+ |> assign(:params, params)
+ |> assign(:is_admin, is_admin)
+ |> assign(:user_changeset, changeset)}
end
@impl true
def handle_params(params, _url, socket) do
- page = list_prompt_requests(params)
- socket = socket |> apply_action(socket.assigns.live_action, params) |> assign(:page, page)
+ page = list_prompt_requests(params) |> subscribe_to_queued_prompts()
+
+ socket =
+ socket
+ |> apply_action(socket.assigns.live_action, params)
+ |> assign(:page, page)
+ |> assign(:params, params)
+
{:noreply, socket}
end
defp apply_action(socket, :edit, %{"id" => id}) do
socket
- |> assign(:page_title, "Edit Prompt request")
+ |> assign(:page_title, "Edit Prompt")
|> assign(:prompt_request, Generator.get_prompt_request!(id))
end
defp apply_action(socket, :new, _params) do
socket
- |> assign(:page_title, "New Prompt request")
+ |> assign(:page_title, "New Prompt")
|> assign(:prompt_request, %PromptRequest{})
end
defp apply_action(socket, :index, _params) do
socket
- |> assign(:page_title, "Listing Prompt requests")
+ |> assign(:page_title, "Listing Prompts")
|> assign(:prompt_request, nil)
end
@impl true
- def handle_event("delete", %{"id" => id}, socket) do
+ def handle_event("delete", %{"id" => id}, %{assigns: %{params: params}} = socket) do
prompt_request = Generator.get_prompt_request!(id)
{:ok, _} = Generator.delete_prompt_request(prompt_request)
- {:noreply, assign(socket, :page, list_prompt_requests(%{"page" => "2"}))}
+ {:noreply, assign(socket, :page, list_prompt_requests(params))}
end
+ def handle_event("upvote", %{"id" => id}, %{assigns: %{user: user, params: params}} = socket) do
+ prompt_request = Generator.get_prompt_request!(id)
+ Diffuser.Accounts.upvote(user, prompt_request)
+
+ {:noreply, assign(socket, :page, list_prompt_requests(params))}
+ end
+
+ def handle_event(
+ "update_user",
+ %{"user" => params},
+ %{assigns: %{user: user}} = socket
+ ) do
+ case Accounts.update_user(user, params) do
+ {:ok, user} ->
+ {:noreply, assign(socket, :user, user)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign(socket, :user_changeset, changeset)}
+ end
+ end
+
+ def handle_event(
+ "search",
+ %{"search" => %{"query" => query}},
+ %{assigns: %{params: params}} = socket
+ ) do
+ params = Map.put(params, :query, query)
+ page = Generator.paginate_prompt_requests(PromptRequest, params)
+ {:noreply, socket |> assign(:params, params) |> assign(:page, page)}
+ end
+
+ def handle_event("clear", _, socket) do
+ page = Generator.paginate_prompt_requests(PromptRequest, %{page: 1})
+ {:noreply, socket |> assign(:params, %{page: 1}) |> assign(:page, page)}
+ end
+
+ def handle_event("order_by", %{"votes" => direction}, %{assigns: %{params: params}} = socket) do
+ direction = if direction in ["asc", "desc"], do: direction, else: nil
+ params = Map.put(params, :order_by, direction)
+ page = Generator.paginate_prompt_requests(PromptRequest, params)
+ {:noreply, socket |> assign(:params, params) |> assign(:page, page)}
+ end
+
+ def handle_event("go_to" = event, %{"jump" => %{"page" => _page} = result}, socket),
+ do: handle_event(event, result, socket)
+
+ def handle_event("go_to", %{"page" => page}, %{assigns: %{params: params}} = socket) do
+ params = Map.put(params, :page, page)
+ page = Generator.paginate_prompt_requests(PromptRequest, params)
+ {:noreply, socket |> assign(:params, params) |> assign(:page, page)}
+ end
+
+ defp has_voted(nil, _), do: false
+
+ defp has_voted(%{id: user_id}, %{votes: prompt_request_votes}) do
+ prompt_request_votes
+ |> Enum.any?(fn %{user_id: id} ->
+ user_id == id
+ end)
+ end
+
+ defp display_current_filters(params) do
+ query = Map.get(params, :query, nil)
+ order_by = Map.get(params, :order_by, nil)
+ page = Map.get(params, :page, 1)
+
+ query_str = if query, do: "Searching for \"#{query}\"", else: "Everything"
+ order_by_str = if order_by == "desc", do: " with most votes", else: ""
+
+ query_str <> order_by_str <> " on page #{page}"
+ end
+
+ defp display_name(%User{username: username}) when not is_nil(username), do: username
+
+ defp display_name(%User{id: id}), do: id |> String.slice(0, 8)
+
+ defp fake_name(), do: for(_ <- 1..8, into: "", do: <
>)
+
defp list_prompt_requests(params) do
- Generator.paginate_prompt_requests(params)
+ Generator.paginate_prompt_requests(PromptRequest, params)
+ end
+
+ defp total_time(%PromptRequest{began_at: began_at, ended_at: ended_at})
+ when not is_nil(began_at) and not is_nil(ended_at) do
+ "Processing Time: #{NaiveDateTime.diff(ended_at, began_at, :second)} seconds"
+ end
+
+ defp total_time(_), do: ""
+
+ defp owns_prompt_request(user, %{user: %NotLoaded{} = prompt_request}),
+ do: user |> owns_prompt_request(prompt_request |> Repo.preload(:user))
+
+ defp owns_prompt_request(nil, _), do: false
+
+ defp owns_prompt_request(_, %{user: nil}), do: false
+
+ defp owns_prompt_request(%{ip_address: their_ip_address, id: their_id}, %{
+ user: %{ip_address: ip_address, id: id}
+ })
+ when their_ip_address == ip_address or their_id == id,
+ do: true
+
+ defp owns_prompt_request(_, _), do: false
+
+ defp subscribe_to_queued_prompts(%Scrivener.Page{entries: entries} = page) do
+ entries
+ |> Enum.each(fn %PromptRequest{id: id, status: status} ->
+ if status in ["queued", "in_progress"] do
+ Endpoint.subscribe("request:#{id}")
+ end
+ end)
+
+ page
+ end
+
+ @impl true
+ def handle_info(
+ %Broadcast{
+ topic: _,
+ event: event,
+ payload: %{prompt_request: %PromptRequest{} = prompt_request}
+ },
+ socket
+ )
+ when event in ["request", "progress"],
+ do: {:noreply, socket |> update_subscribed_prompt(prompt_request)}
+
+ defp update_subscribed_prompt(
+ %{assigns: %{page: %Scrivener.Page{entries: entries} = page}} = socket,
+ %PromptRequest{id: id} = prompt_request
+ ) do
+ entries =
+ entries
+ |> Enum.map(
+ &if &1.id === id, do: prompt_request |> Repo.preload([:votes, :user, :images]), else: &1
+ )
+
+ page = Map.put(page, :entries, entries)
+
+ socket
+ |> assign(:page, page)
end
end
diff --git a/lib/diffuser_web/live/prompt_request_live/index.html.heex b/lib/diffuser_web/live/prompt_request_live/index.html.heex
index 2cbc9ba..92d472d 100644
--- a/lib/diffuser_web/live/prompt_request_live/index.html.heex
+++ b/lib/diffuser_web/live/prompt_request_live/index.html.heex
@@ -1,5 +1,3 @@
-Listing Prompt requests
-
<%= if @live_action in [:new, :edit] do %>
<.modal return_to={Routes.prompt_request_index_path(@socket, :index)}>
<.live_component
@@ -7,52 +5,134 @@
id={@prompt_request.id || :new}
title={@page_title}
action={@live_action}
- prompt_request={@prompt_request}}
+ prompt_request={@prompt_request}
+ user={@user}}
/>
<% end %>
-
-
-
- Image |
- Prompt |
+<%= if @user do %>
+
+
+
+
+ Info
+
+
+
IP Address: <%= @user.ip_address %>
+
Yes. Your server, Silas, will keep a record of which IP sent what.
-
|
-
-
-
- <%= for prompt_request <- @page.entries do %>
-
-
- <%= for result <- prompt_request.images do %>
-
- <% end %>
- |
- <%= prompt_request.prompt %> |
+ Username: <%= display_name(@user) %>
+ <.form let={f} for={@user_changeset} phx-submit="update_user">
+
+
+ <%= text_input f, :username, class: "input input-bordered input-secondary" %>
+ <%= error_tag f, :username %>
-
- <%= live_redirect "Show", to: Routes.prompt_request_show_path(@socket, :show, prompt_request) %>
- <%= live_patch "Edit", to: Routes.prompt_request_index_path(@socket, :edit, prompt_request) %>
- <%= link "Delete", to: "#", phx_click: "delete", phx_value_id: prompt_request.id, data: [confirm: "Are you sure?"] %>
- |
-
- <% end %>
-
-
+ <%= submit "Save Username", class: "btn" %>
+
+
-
+
+
+<% end %>
+
+
+
<%= display_current_filters(@params) %>
-<%= live_patch "New Prompt request", to: Routes.prompt_request_index_path(@socket, :new) %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= for prompt_request <- @page.entries do %>
+
+ <% result = if prompt_request.images |> length > 0, do: prompt_request.images |> List.first() %>
+ <%= if result do %>
+

+ <% else %>
+
<%= prompt_request.status %>, <%= prompt_request.completed_steps %>/<%= prompt_request.steps %>
+ <% end %>
+
+
<%= prompt_request.prompt %>
+
Steps: <%= prompt_request.steps %>, Guidance Scale: <%= prompt_request.guidance_scale %>
+ <%= if prompt_request.status == "finished" do %>
+ <%= total_time(prompt_request) %>
+ <% end %>
+
+ <%= case prompt_request.user do %>
+ <%= %NotLoaded{} -> %>
+ <% user -> %>
+ <%= if user do %>
+ Created by: <%= display_name(user) %> <%= if @is_admin, do: "(#{user.ip_address}, #{prompt_request.code})" %>
+ <% end %>
+ <% end %>
+
+ Votes: <%= prompt_request.votes |> Enum.count() %>
+
+ <%= if has_voted(@user, prompt_request) do %>
+ <% else %>
+ <%= link "Upvote", to: "#", phx_click: "upvote", phx_value_id: prompt_request.id %>
+ <% end %>
+
+
+
+ <%= if prompt_request.status == "finished" and (@is_admin or owns_prompt_request(@user, prompt_request)) do %>
+
+ <%= link "Delete", to: "#", phx_click: "delete", phx_value_id: prompt_request.id, data: [confirm: "Are you sure?"] %>
+
+ <% end %>
+
+
+ <% end %>
+
+
+
+
+ Total results: <%= @page.total_entries %>
+
+
+
diff --git a/lib/diffuser_web/live/prompt_request_live/show.ex b/lib/diffuser_web/live/prompt_request_live/show.ex
index a22dcd1..ba81fc4 100644
--- a/lib/diffuser_web/live/prompt_request_live/show.ex
+++ b/lib/diffuser_web/live/prompt_request_live/show.ex
@@ -1,13 +1,11 @@
defmodule DiffuserWeb.PromptRequestLive.Show do
use DiffuserWeb, :live_view
- import Ecto.Query
alias Diffuser.Generator
- alias Diffuser.Generator.{Image, PromptRequest}
+ alias Diffuser.Generator.PromptRequest
alias Diffuser.Repo
alias Phoenix.Socket.Broadcast
alias DiffuserWeb.Endpoint
- alias Ecto.Association.NotLoaded
@impl true
def mount(%{"id" => id}, _session, socket) do
diff --git a/lib/diffuser_web/live/prompt_request_live/show.html.heex b/lib/diffuser_web/live/prompt_request_live/show.html.heex
index 6efc2b8..3403436 100644
--- a/lib/diffuser_web/live/prompt_request_live/show.html.heex
+++ b/lib/diffuser_web/live/prompt_request_live/show.html.heex
@@ -1,18 +1,5 @@
Show Prompt request
-<%= if @live_action in [:edit] do %>
- <.modal return_to={Routes.prompt_request_show_path(@socket, :show, @prompt_request)}>
- <.live_component
- module={DiffuserWeb.PromptRequestLive.FormComponent}
- id={@prompt_request.id}
- title={@page_title}
- action={@live_action}
- prompt_request={@prompt_request}
- return_to={Routes.prompt_request_show_path(@socket, :show, @prompt_request)}
- />
-
-<% end %>
-
-<%= live_patch "Edit", to: Routes.prompt_request_show_path(@socket, :edit, @prompt_request), class: "button" %> |
<%= live_redirect "Back", to: Routes.prompt_request_index_path(@socket, :index) %>
diff --git a/lib/diffuser_web/plugs/public_ip.ex b/lib/diffuser_web/plugs/public_ip.ex
new file mode 100644
index 0000000..a3f573f
--- /dev/null
+++ b/lib/diffuser_web/plugs/public_ip.ex
@@ -0,0 +1,67 @@
+defmodule DiffuserWeb.Plugs.PublicIp do
+ @moduledoc "Get public IP address of request from x-forwarded-for header"
+
+ def init(opts), do: opts
+
+ def call(%{assigns: %{ip: _}} = conn, _opts), do: conn
+
+ def call(conn, _opts) do
+ process(conn, Plug.Conn.get_req_header(conn, "x-forwarded-for"))
+ end
+
+ def process(%{remote_ip: remote_ip} = conn, []) do
+ Plug.Conn.assign(conn, :ip, to_string(:inet_parse.ntoa(remote_ip)))
+ end
+
+ def process(conn, vals) do
+ # Rewrite standard remote_ip field with value from header
+ ip_address = get_ip_address(conn, vals)
+ # See https://hexdocs.pm/plug/Plug.Conn.html
+ conn = %{conn | remote_ip: ip_address}
+
+ Plug.Conn.assign(conn, :ip, to_string(:inet_parse.ntoa(ip_address)))
+ end
+
+ defp get_ip_address(conn, vals)
+ defp get_ip_address(%{remote_ip: remote_ip}, []), do: remote_ip
+
+ defp get_ip_address(%{remote_ip: remote_ip} = _conn, [val | _]) do
+ # Split into multiple values
+ comps =
+ val
+ |> String.split(~r{\s*,\s*}, trim: true)
+ # Get rid of "unknown" values
+ |> Enum.filter(&(&1 != "unknown"))
+ # Split IP from port, if any
+ |> Enum.map(&hd(String.split(&1, ":")))
+ # Filter out blanks
+ |> Enum.filter(&(&1 != ""))
+ # Parse address into :inet.ip_address tuple
+ |> Enum.map(&parse_address(&1))
+ # Elminate internal IP addreses, e.g. 192.168.1.1
+ |> Enum.filter(&is_public_ip(&1))
+
+ case comps do
+ [] -> remote_ip
+ [comp | _] -> comp
+ end
+ end
+
+ defp parse_address(ip) do
+ case :inet.parse_ipv4strict_address(to_charlist(ip)) do
+ {:ok, ip_address} -> ip_address
+ {:error, :einval} -> :einval
+ end
+ end
+
+ defp is_public_ip(ip_address) do
+ case ip_address do
+ {10, _, _, _} -> false
+ {192, 168, _, _} -> false
+ {172, second, _, _} when second >= 16 and second <= 31 -> false
+ {127, 0, 0, _} -> false
+ {_, _, _, _} -> true
+ :einval -> false
+ end
+ end
+end
diff --git a/lib/diffuser_web/plugs/user_auth.ex b/lib/diffuser_web/plugs/user_auth.ex
new file mode 100644
index 0000000..057a05f
--- /dev/null
+++ b/lib/diffuser_web/plugs/user_auth.ex
@@ -0,0 +1,37 @@
+defmodule DiffuserWeb.Plugs.UserAuth do
+ use DiffuserWeb, :controller
+ import Plug.Conn
+ alias Diffuser.Accounts
+ alias Diffuser.Repo
+
+ def init(default), do: default
+
+ def call(%{remote_ip: remote_ip} = conn, _) do
+ remote_ip = remote_ip |> :inet_parse.ntoa() |> to_string()
+
+ case get_session(conn, :user) do
+ nil ->
+ with {:ok, user} <- Accounts.create_user(%{ip_address: remote_ip}) do
+ put_session(conn, :user, user |> Repo.preload(votes: [:prompt_request]))
+ else
+ {:error, _changeset} -> put_session(conn, :user, nil)
+ end
+
+ %Accounts.User{ip_address: user_ip} = user ->
+ with false <- user_ip == remote_ip,
+ {:ok, user} <- Accounts.update_user(user, %{ip_address: remote_ip}) do
+ conn |> put_session(:user, user |> Repo.preload(votes: [:prompt_request]))
+ else
+ true ->
+ put_session(
+ conn,
+ :user,
+ Accounts.get_user!(user.id) |> Repo.preload(votes: [:prompt_request])
+ )
+
+ _ ->
+ put_session(conn, :user, nil)
+ end
+ end
+ end
+end
diff --git a/lib/diffuser_web/plugs/validate_admin.ex b/lib/diffuser_web/plugs/validate_admin.ex
new file mode 100644
index 0000000..36a70d7
--- /dev/null
+++ b/lib/diffuser_web/plugs/validate_admin.ex
@@ -0,0 +1,16 @@
+defmodule DiffuserWeb.Plugs.ValidateAdmin do
+ use DiffuserWeb, :controller
+ import Plug.Conn
+
+ def init(default), do: default
+
+ def call(%{remote_ip: remote_ip} = conn, _) do
+ remote_ip = remote_ip |> :inet_parse.ntoa() |> to_string()
+
+ if remote_ip == "127.0.0.1" do
+ conn |> put_session(:is_admin, true)
+ else
+ conn |> put_session(:is_admin, false)
+ end
+ end
+end
diff --git a/lib/diffuser_web/router.ex b/lib/diffuser_web/router.ex
index 9260602..6c9927a 100644
--- a/lib/diffuser_web/router.ex
+++ b/lib/diffuser_web/router.ex
@@ -1,5 +1,6 @@
defmodule DiffuserWeb.Router do
use DiffuserWeb, :router
+ alias DiffuserWeb.Plugs.{PublicIp, UserAuth, ValidateAdmin}
pipeline :browser do
plug :accepts, ["html"]
@@ -14,17 +15,18 @@ defmodule DiffuserWeb.Router do
plug :accepts, ["json"]
end
+ pipeline :authenticate do
+ plug PublicIp
+ plug UserAuth
+ plug ValidateAdmin
+ end
+
scope "/", DiffuserWeb do
pipe_through :browser
+ pipe_through :authenticate
- get "/", PageController, :index
-
- live "/prompt_requests", PromptRequestLive.Index, :index
- live "/prompt_requests/new", PromptRequestLive.Index, :new
- live "/prompt_requests/:id/edit", PromptRequestLive.Index, :edit
-
- live "/prompt_requests/:id", PromptRequestLive.Show, :show
- live "/prompt_requests/:id/show/edit", PromptRequestLive.Show, :edit
+ live "/", PromptRequestLive.Index, :index
+ live "/new", PromptRequestLive.Index, :new
end
# Other scopes may use custom stacks.
@@ -44,6 +46,7 @@ defmodule DiffuserWeb.Router do
scope "/" do
pipe_through :browser
+ pipe_through :authenticate
live_dashboard "/dashboard", metrics: DiffuserWeb.Telemetry
end
diff --git a/lib/diffuser_web/templates/layout/live.html.heex b/lib/diffuser_web/templates/layout/live.html.heex
index a29d604..17a2a87 100644
--- a/lib/diffuser_web/templates/layout/live.html.heex
+++ b/lib/diffuser_web/templates/layout/live.html.heex
@@ -1,8 +1,4 @@
- <%= live_flash(@flash, :info) %>
-
<%= live_flash(@flash, :error) %>
diff --git a/lib/diffuser_web/templates/layout/root.html.heex b/lib/diffuser_web/templates/layout/root.html.heex
index d78a398..fa643f0 100644
--- a/lib/diffuser_web/templates/layout/root.html.heex
+++ b/lib/diffuser_web/templates/layout/root.html.heex
@@ -1,30 +1,32 @@
-
+
-
+
- <%= live_title_tag assigns[:page_title] || "Diffuser", suffix: " · Phoenix Framework" %>
+ <%= live_title_tag assigns[:page_title] || "Diffuser" %>
-
-
-
-
-
-
-
-
<%= @inner_content %>
+
+
diff --git a/mix.exs b/mix.exs
index bc8d088..414f4a9 100644
--- a/mix.exs
+++ b/mix.exs
@@ -53,7 +53,8 @@ defmodule Diffuser.MixProject do
{:erlport, "~> 0.10.1"},
{:waffle, "~> 1.1"},
{:waffle_ecto, "~> 0.0.11"},
- {:scrivener_ecto, "~> 2.7"}
+ {:scrivener_ecto, "~> 2.7"},
+ {:tailwind, "~> 0.1.8", runtime: Mix.env() == :dev}
]
end
@@ -69,7 +70,8 @@ defmodule Diffuser.MixProject do
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
- "assets.deploy": ["esbuild default --minify", "phx.digest"]
+ # "assets.deploy": ["esbuild default --minify", "phx.digest"],
+ "assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]
]
end
end
diff --git a/mix.lock b/mix.lock
index 4874a7b..303c625 100644
--- a/mix.lock
+++ b/mix.lock
@@ -39,6 +39,7 @@
"scrivener_ecto": {:hex, :scrivener_ecto, "2.7.0", "cf64b8cb8a96cd131cdbcecf64e7fd395e21aaa1cb0236c42a7c2e34b0dca580", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm", "e809f171687806b0031129034352f5ae44849720c48dd839200adeaf0ac3e260"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"swoosh": {:hex, :swoosh, "1.7.4", "f967d9b2659e81bab241b96267aae1001d35c2beea2df9c03dcf47b007bf566f", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1553d994b4cf069162965e63de1e1c53d8236e127118d21e56ce2abeaa3f25b4"},
+ "tailwind": {:hex, :tailwind, "0.1.8", "3762defebc8e328fb19ff1afb8c37723e53b52be5ca74f0b8d0a02d1f3f432cf", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "40061d1bf2c0505c6b87be7a3ed05243fc10f6e1af4bac3336db8358bc84d4cc"},
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
diff --git a/priv/python/requirements.txt b/priv/python/requirements.txt
index 42f877b..0c998fa 100644
--- a/priv/python/requirements.txt
+++ b/priv/python/requirements.txt
@@ -6,3 +6,4 @@ tqdm==4.64.0
openvino==2022.1.0
huggingface_hub==0.9.0
scipy==1.9.0
+ftfy==6.1.1
diff --git a/priv/python/stable_diffusion.py b/priv/python/stable_diffusion.py
index f030a6c..ef7350e 100755
--- a/priv/python/stable_diffusion.py
+++ b/priv/python/stable_diffusion.py
@@ -42,8 +42,8 @@ class StableDiffusion:
self.latent_shape = tuple(self._unet.inputs[0].shape)[1:]
# decoder
self._vae = self.core.read_model(
- hf_hub_download(repo_id=model, filename="vae.xml"),
- hf_hub_download(repo_id=model, filename="vae.bin")
+ hf_hub_download(repo_id=model, filename="vae_decoder.xml"),
+ hf_hub_download(repo_id=model, filename="vae_decoder.bin")
)
self.vae = self.core.compile_model(self._vae, device)
diff --git a/priv/repo/migrations/20220901162720_add_completed_steps_to_prompt_request.exs b/priv/repo/migrations/20220901162720_add_completed_steps_to_prompt_request.exs
new file mode 100644
index 0000000..87dda8d
--- /dev/null
+++ b/priv/repo/migrations/20220901162720_add_completed_steps_to_prompt_request.exs
@@ -0,0 +1,9 @@
+defmodule Diffuser.Repo.Migrations.AddCompletedStepsToPromptRequest do
+ use Ecto.Migration
+
+ def change do
+ alter table(:prompt_requests) do
+ add :completed_steps, :integer
+ end
+ end
+end
diff --git a/priv/repo/migrations/20220901185044_add_queue_timestamps_to_prompt_request.exs b/priv/repo/migrations/20220901185044_add_queue_timestamps_to_prompt_request.exs
new file mode 100644
index 0000000..589df02
--- /dev/null
+++ b/priv/repo/migrations/20220901185044_add_queue_timestamps_to_prompt_request.exs
@@ -0,0 +1,10 @@
+defmodule Diffuser.Repo.Migrations.AddQueueTimestampsToPromptRequest do
+ use Ecto.Migration
+
+ def change do
+ alter table(:prompt_requests) do
+ add :began_at, :naive_datetime
+ add :ended_at, :naive_datetime
+ end
+ end
+end
diff --git a/priv/repo/migrations/20220904223206_add_user_table.exs b/priv/repo/migrations/20220904223206_add_user_table.exs
new file mode 100644
index 0000000..1f0e579
--- /dev/null
+++ b/priv/repo/migrations/20220904223206_add_user_table.exs
@@ -0,0 +1,11 @@
+defmodule Diffuser.Repo.Migrations.AddUserTable do
+ use Ecto.Migration
+
+ def change do
+ create table(:users) do
+ add :ip_address, :string
+ add :username, :string
+ timestamps()
+ end
+ end
+end
diff --git a/priv/repo/migrations/20220905174728_create_votes.exs b/priv/repo/migrations/20220905174728_create_votes.exs
new file mode 100644
index 0000000..3867354
--- /dev/null
+++ b/priv/repo/migrations/20220905174728_create_votes.exs
@@ -0,0 +1,13 @@
+defmodule Diffuser.Repo.Migrations.CreateVotes do
+ use Ecto.Migration
+
+ def change do
+ create table(:votes) do
+ add :prompt_request_id, references(:prompt_requests, on_delete: :nothing)
+
+ timestamps()
+ end
+
+ create index(:votes, [:prompt_request_id])
+ end
+end
diff --git a/priv/repo/migrations/20220905181641_add_user_to_votes.exs b/priv/repo/migrations/20220905181641_add_user_to_votes.exs
new file mode 100644
index 0000000..f731186
--- /dev/null
+++ b/priv/repo/migrations/20220905181641_add_user_to_votes.exs
@@ -0,0 +1,9 @@
+defmodule Diffuser.Repo.Migrations.AddUserToVotes do
+ use Ecto.Migration
+
+ def change do
+ alter table(:votes) do
+ add :user_id, references(:users)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20220905223138_add_user_to_prompt_requests.exs b/priv/repo/migrations/20220905223138_add_user_to_prompt_requests.exs
new file mode 100644
index 0000000..674b3c8
--- /dev/null
+++ b/priv/repo/migrations/20220905223138_add_user_to_prompt_requests.exs
@@ -0,0 +1,9 @@
+defmodule Diffuser.Repo.Migrations.AddUserToPromptRequests do
+ use Ecto.Migration
+
+ def change do
+ alter table(:prompt_requests) do
+ add :user_id, references(:users)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20220907165652_add_code_to_users.exs b/priv/repo/migrations/20220907165652_add_code_to_users.exs
new file mode 100644
index 0000000..b61983d
--- /dev/null
+++ b/priv/repo/migrations/20220907165652_add_code_to_users.exs
@@ -0,0 +1,9 @@
+defmodule Diffuser.Repo.Migrations.AddCodeToUsers do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add :code, :string
+ end
+ end
+end
diff --git a/priv/repo/migrations/20220907174050_add_code_to_prompt_requests.exs b/priv/repo/migrations/20220907174050_add_code_to_prompt_requests.exs
new file mode 100644
index 0000000..a5f5f42
--- /dev/null
+++ b/priv/repo/migrations/20220907174050_add_code_to_prompt_requests.exs
@@ -0,0 +1,9 @@
+defmodule Diffuser.Repo.Migrations.AddCodeToPromptRequests do
+ use Ecto.Migration
+
+ def change do
+ alter table(:prompt_requests) do
+ add :code, :string
+ end
+ end
+end
diff --git a/priv/static/favicon-a8ca4e3a2bb8fea46a9ee9e102e7d3eb.ico b/priv/static/favicon-a8ca4e3a2bb8fea46a9ee9e102e7d3eb.ico
new file mode 100644
index 0000000..73de524
Binary files /dev/null and b/priv/static/favicon-a8ca4e3a2bb8fea46a9ee9e102e7d3eb.ico differ
diff --git a/priv/static/images/GitHub-Mark-Light-120px-plus.png b/priv/static/images/GitHub-Mark-Light-120px-plus.png
new file mode 100644
index 0000000..192846a
Binary files /dev/null and b/priv/static/images/GitHub-Mark-Light-120px-plus.png differ
diff --git a/priv/static/images/phoenix-5bd99a0d17dd41bc9d9bf6840abcc089.png b/priv/static/images/phoenix-5bd99a0d17dd41bc9d9bf6840abcc089.png
new file mode 100644
index 0000000..9c81075
Binary files /dev/null and b/priv/static/images/phoenix-5bd99a0d17dd41bc9d9bf6840abcc089.png differ
diff --git a/priv/static/robots-9e2c81b0855bbff2baa8371bc4a78186.txt b/priv/static/robots-9e2c81b0855bbff2baa8371bc4a78186.txt
new file mode 100644
index 0000000..26e06b5
--- /dev/null
+++ b/priv/static/robots-9e2c81b0855bbff2baa8371bc4a78186.txt
@@ -0,0 +1,5 @@
+# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
+#
+# To ban all spiders from the entire site uncomment the next two lines:
+# User-agent: *
+# Disallow: /
diff --git a/priv/static/robots-9e2c81b0855bbff2baa8371bc4a78186.txt.gz b/priv/static/robots-9e2c81b0855bbff2baa8371bc4a78186.txt.gz
new file mode 100644
index 0000000..043be33
Binary files /dev/null and b/priv/static/robots-9e2c81b0855bbff2baa8371bc4a78186.txt.gz differ
diff --git a/priv/static/robots.txt.gz b/priv/static/robots.txt.gz
new file mode 100644
index 0000000..043be33
Binary files /dev/null and b/priv/static/robots.txt.gz differ
diff --git a/test/diffuser/accounts_test.exs b/test/diffuser/accounts_test.exs
new file mode 100644
index 0000000..c7caa80
--- /dev/null
+++ b/test/diffuser/accounts_test.exs
@@ -0,0 +1,130 @@
+defmodule Diffuser.AccountsTest do
+ use Diffuser.DataCase
+
+ alias Diffuser.Accounts
+
+ describe "users" do
+ alias Diffuser.Accounts.User
+
+ import Diffuser.AccountsFixtures
+
+ @invalid_attrs %{}
+
+ test "list_users/0 returns all users" do
+ user = user_fixture()
+ assert Accounts.list_users() == [user]
+ end
+
+ test "get_user!/1 returns the user with given id" do
+ user = user_fixture()
+ assert Accounts.get_user!(user.id) == user
+ end
+
+ test "create_user/1 with valid data creates a user" do
+ valid_attrs = %{}
+
+ assert {:ok, %User{} = user} = Accounts.create_user(valid_attrs)
+ end
+
+ test "create_user/1 with invalid data returns error changeset" do
+ assert {:error, %Ecto.Changeset{}} = Accounts.create_user(@invalid_attrs)
+ end
+
+ test "update_user/2 with valid data updates the user" do
+ user = user_fixture()
+ update_attrs = %{}
+
+ assert {:ok, %User{} = user} = Accounts.update_user(user, update_attrs)
+ end
+
+ test "update_user/2 with invalid data returns error changeset" do
+ user = user_fixture()
+ assert {:error, %Ecto.Changeset{}} = Accounts.update_user(user, @invalid_attrs)
+ assert user == Accounts.get_user!(user.id)
+ end
+
+ test "delete_user/1 deletes the user" do
+ user = user_fixture()
+ assert {:ok, %User{}} = Accounts.delete_user(user)
+ assert_raise Ecto.NoResultsError, fn -> Accounts.get_user!(user.id) end
+ end
+
+ test "change_user/1 returns a user changeset" do
+ user = user_fixture()
+ assert %Ecto.Changeset{} = Accounts.change_user(user)
+ end
+ end
+
+ describe "votes" do
+ alias Diffuser.Accounts.Vote
+
+ import Diffuser.AccountsFixtures
+
+ @invalid_attrs %{}
+
+ test "list_votes/0 returns all votes" do
+ vote = vote_fixture()
+ assert Accounts.list_votes() == [vote]
+ end
+
+ test "get_vote!/1 returns the vote with given id" do
+ vote = vote_fixture()
+ assert Accounts.get_vote!(vote.id) == vote
+ end
+
+ test "create_vote/1 with valid data creates a vote" do
+ valid_attrs = %{}
+
+ assert {:ok, %Vote{} = vote} = Accounts.create_vote(valid_attrs)
+ end
+
+ test "create_vote/1 with invalid data returns error changeset" do
+ assert {:error, %Ecto.Changeset{}} = Accounts.create_vote(@invalid_attrs)
+ end
+
+ test "update_vote/2 with valid data updates the vote" do
+ vote = vote_fixture()
+ update_attrs = %{}
+
+ assert {:ok, %Vote{} = vote} = Accounts.update_vote(vote, update_attrs)
+ end
+
+ test "update_vote/2 with invalid data returns error changeset" do
+ vote = vote_fixture()
+ assert {:error, %Ecto.Changeset{}} = Accounts.update_vote(vote, @invalid_attrs)
+ assert vote == Accounts.get_vote!(vote.id)
+ end
+
+ test "delete_vote/1 deletes the vote" do
+ vote = vote_fixture()
+ assert {:ok, %Vote{}} = Accounts.delete_vote(vote)
+ assert_raise Ecto.NoResultsError, fn -> Accounts.get_vote!(vote.id) end
+ end
+
+ test "change_vote/1 returns a vote changeset" do
+ vote = vote_fixture()
+ assert %Ecto.Changeset{} = Accounts.change_vote(vote)
+ end
+ end
+
+ describe "upvote/2" do
+ alias Diffuser.Accounts.Vote
+
+ import Diffuser.AccountsFixtures
+ import Diffuser.GeneratorFixtures
+
+ test "creates upvote and associates it to specific prompt" do
+ %{id: pr_id} = pr = prompt_request_fixture()
+
+ assert {:ok, %Vote{prompt_request_id: ^pr_id}} = Accounts.upvote(pr)
+
+ %{votes: votes} = Diffuser.Generator.get_prompt_request!(pr_id) |> Repo.preload(:votes)
+ assert votes |> length() == 1
+ end
+
+ test "creates upvote and associates it to current user" do
+
+
+ end
+ end
+end
diff --git a/test/diffuser_web/live/prompt_request_live_test.exs b/test/diffuser_web/live/prompt_request_live_test.exs
index f3def02..bed44d5 100644
--- a/test/diffuser_web/live/prompt_request_live_test.exs
+++ b/test/diffuser_web/live/prompt_request_live_test.exs
@@ -3,6 +3,7 @@ defmodule DiffuserWeb.PromptRequestLiveTest do
import Phoenix.LiveViewTest
import Diffuser.GeneratorFixtures
+ alias DiffuserWeb.Plugs.UserAuth
@create_attrs %{prompt: "some prompt"}
@update_attrs %{prompt: "some updated prompt"}
@@ -26,8 +27,8 @@ defmodule DiffuserWeb.PromptRequestLiveTest do
test "saves new prompt_request", %{conn: conn} do
{:ok, index_live, _html} = live(conn, Routes.prompt_request_index_path(conn, :index))
- assert index_live |> element("a", "New Prompt request") |> render_click() =~
- "New Prompt request"
+ assert index_live |> element("a", "New Prompt") |> render_click() =~
+ "New Prompt"
assert_patch(index_live, Routes.prompt_request_index_path(conn, :new))
@@ -39,16 +40,18 @@ defmodule DiffuserWeb.PromptRequestLiveTest do
index_live
|> form("#prompt_request-form", prompt_request: @create_attrs)
|> render_submit()
- |> follow_redirect(conn, Routes.prompt_request_index_path(conn, :index))
+ |> follow_redirect(conn, "#{Routes.prompt_request_index_path(conn, :index)}?page=1")
- assert html =~ "Prompt request created successfully"
assert html =~ "some prompt"
+ # assert html =~ user.id
end
test "updates prompt_request in listing", %{conn: conn, prompt_request: prompt_request} do
{:ok, index_live, _html} = live(conn, Routes.prompt_request_index_path(conn, :index))
- assert index_live |> element("#prompt_request-#{prompt_request.id} a", "Edit") |> render_click() =~
+ assert index_live
+ |> element("#prompt_request-#{prompt_request.id} a", "Edit")
+ |> render_click() =~
"Edit Prompt request"
assert_patch(index_live, Routes.prompt_request_index_path(conn, :edit, prompt_request))
@@ -70,7 +73,10 @@ defmodule DiffuserWeb.PromptRequestLiveTest do
test "deletes prompt_request in listing", %{conn: conn, prompt_request: prompt_request} do
{:ok, index_live, _html} = live(conn, Routes.prompt_request_index_path(conn, :index))
- assert index_live |> element("#prompt_request-#{prompt_request.id} a", "Delete") |> render_click()
+ assert index_live
+ |> element("#prompt_request-#{prompt_request.id} a", "Delete")
+ |> render_click()
+
refute has_element?(index_live, "#prompt_request-#{prompt_request.id}")
end
end
@@ -79,14 +85,16 @@ defmodule DiffuserWeb.PromptRequestLiveTest do
setup [:create_prompt_request]
test "displays prompt_request", %{conn: conn, prompt_request: prompt_request} do
- {:ok, _show_live, html} = live(conn, Routes.prompt_request_show_path(conn, :show, prompt_request))
+ {:ok, _show_live, html} =
+ live(conn, Routes.prompt_request_show_path(conn, :show, prompt_request))
assert html =~ "Show Prompt request"
assert html =~ prompt_request.prompt
end
test "updates prompt_request within modal", %{conn: conn, prompt_request: prompt_request} do
- {:ok, show_live, _html} = live(conn, Routes.prompt_request_show_path(conn, :show, prompt_request))
+ {:ok, show_live, _html} =
+ live(conn, Routes.prompt_request_show_path(conn, :show, prompt_request))
assert show_live |> element("a", "Edit") |> render_click() =~
"Edit Prompt request"
diff --git a/test/support/fixtures/accounts_fixtures.ex b/test/support/fixtures/accounts_fixtures.ex
new file mode 100644
index 0000000..9a91b13
--- /dev/null
+++ b/test/support/fixtures/accounts_fixtures.ex
@@ -0,0 +1,34 @@
+defmodule Diffuser.AccountsFixtures do
+ @moduledoc """
+ This module defines test helpers for creating
+ entities via the `Diffuser.Accounts` context.
+ """
+
+ @doc """
+ Generate a user.
+ """
+ def user_fixture(attrs \\ %{}) do
+ {:ok, user} =
+ attrs
+ |> Enum.into(%{
+
+ })
+ |> Diffuser.Accounts.create_user()
+
+ user
+ end
+
+ @doc """
+ Generate a vote.
+ """
+ def vote_fixture(attrs \\ %{}) do
+ {:ok, vote} =
+ attrs
+ |> Enum.into(%{
+
+ })
+ |> Diffuser.Accounts.create_vote()
+
+ vote
+ end
+end