Spaces:
Sleeping
Sleeping
Commit
·
b6f0124
0
Parent(s):
Initial commit
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .formatter.exs +5 -0
- .gitignore +36 -0
- README.md +1 -0
- assets/css/app.css +5 -0
- assets/js/app.js +26 -0
- assets/tailwind.config.js +75 -0
- assets/vendor/topbar.js +165 -0
- config/config.exs +45 -0
- config/dev.exs +38 -0
- config/prod.exs +18 -0
- config/runtime.exs +25 -0
- config/test.exs +14 -0
- lib/chai.ex +2 -0
- lib/chai/application.ex +28 -0
- lib/chai_web.ex +111 -0
- lib/chai_web/components/core_components.ex +137 -0
- lib/chai_web/components/layouts.ex +5 -0
- lib/chai_web/components/layouts/app.html.heex +31 -0
- lib/chai_web/components/layouts/root.html.heex +18 -0
- lib/chai_web/controllers/error_html.ex +7 -0
- lib/chai_web/endpoint.ex +44 -0
- lib/chai_web/router.ex +31 -0
- lib/chai_web/telemetry.ex +69 -0
- mix.exs +51 -0
- mix.lock +28 -0
- priv/hero_icons/LICENSE.md +21 -0
- priv/hero_icons/UPGRADE.md +7 -0
- priv/hero_icons/optimized/20/solid/academic-cap.svg +3 -0
- priv/hero_icons/optimized/20/solid/adjustments-horizontal.svg +3 -0
- priv/hero_icons/optimized/20/solid/adjustments-vertical.svg +3 -0
- priv/hero_icons/optimized/20/solid/archive-box-arrow-down.svg +3 -0
- priv/hero_icons/optimized/20/solid/archive-box-x-mark.svg +4 -0
- priv/hero_icons/optimized/20/solid/archive-box.svg +4 -0
- priv/hero_icons/optimized/20/solid/arrow-down-circle.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-down-left.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-down-on-square-stack.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-down-on-square.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-down-right.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-down-tray.svg +4 -0
- priv/hero_icons/optimized/20/solid/arrow-down.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-left-circle.svg +10 -0
- priv/hero_icons/optimized/20/solid/arrow-left-on-rectangle.svg +4 -0
- priv/hero_icons/optimized/20/solid/arrow-left.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-long-down.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-long-left.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-long-right.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-long-up.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-path-rounded-square.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-path.svg +3 -0
- priv/hero_icons/optimized/20/solid/arrow-right-circle.svg +3 -0
.formatter.exs
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
import_deps: [:phoenix],
|
3 |
+
plugins: [Phoenix.LiveView.HTMLFormatter],
|
4 |
+
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"]
|
5 |
+
]
|
.gitignore
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# The directory Mix will write compiled artifacts to.
|
2 |
+
/_build/
|
3 |
+
|
4 |
+
# If you run "mix test --cover", coverage assets end up here.
|
5 |
+
/cover/
|
6 |
+
|
7 |
+
# The directory Mix downloads your dependencies sources to.
|
8 |
+
/deps/
|
9 |
+
|
10 |
+
# Where 3rd-party dependencies like ExDoc output generated docs.
|
11 |
+
/doc/
|
12 |
+
|
13 |
+
# Ignore .fetch files in case you like to edit your project deps locally.
|
14 |
+
/.fetch
|
15 |
+
|
16 |
+
# If the VM crashes, it generates a dump, let's ignore it too.
|
17 |
+
erl_crash.dump
|
18 |
+
|
19 |
+
# Also ignore archive artifacts (built via "mix archive.build").
|
20 |
+
*.ez
|
21 |
+
|
22 |
+
# Temporary files, for example, from tests.
|
23 |
+
/tmp/
|
24 |
+
|
25 |
+
# Ignore package tarball (built via "mix hex.build").
|
26 |
+
chai-*.tar
|
27 |
+
|
28 |
+
# Ignore assets that are produced by build tools.
|
29 |
+
/priv/static/assets/
|
30 |
+
|
31 |
+
# Ignore digested assets cache.
|
32 |
+
/priv/static/cache_manifest.json
|
33 |
+
|
34 |
+
# In case you use Node.js/npm, you want to ignore these.
|
35 |
+
npm-debug.log
|
36 |
+
/assets/node_modules/
|
README.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Chai 🍵
|
assets/css/app.css
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import "tailwindcss/base";
|
2 |
+
@import "tailwindcss/components";
|
3 |
+
@import "tailwindcss/utilities";
|
4 |
+
|
5 |
+
/* This file is for your main application CSS */
|
assets/js/app.js
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import "phoenix_html";
|
2 |
+
import { Socket } from "phoenix";
|
3 |
+
import { LiveSocket } from "phoenix_live_view";
|
4 |
+
import topbar from "../vendor/topbar";
|
5 |
+
|
6 |
+
let csrfToken = document
|
7 |
+
.querySelector("meta[name='csrf-token']")
|
8 |
+
.getAttribute("content");
|
9 |
+
|
10 |
+
let liveSocket = new LiveSocket("/live", Socket, {
|
11 |
+
params: { _csrf_token: csrfToken },
|
12 |
+
});
|
13 |
+
|
14 |
+
// Show progress bar on live navigation and form submits
|
15 |
+
topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" });
|
16 |
+
window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300));
|
17 |
+
window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide());
|
18 |
+
|
19 |
+
// Connect if there are any LiveViews on the page
|
20 |
+
liveSocket.connect();
|
21 |
+
|
22 |
+
// Expose liveSocket on window for web console debug logs and latency simulation:
|
23 |
+
// >> liveSocket.enableDebug()
|
24 |
+
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
25 |
+
// >> liveSocket.disableLatencySim()
|
26 |
+
window.liveSocket = liveSocket;
|
assets/tailwind.config.js
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const plugin = require("tailwindcss/plugin");
|
2 |
+
const fs = require("fs");
|
3 |
+
const path = require("path");
|
4 |
+
|
5 |
+
module.exports = {
|
6 |
+
content: ["./js/**/*.js", "../lib/*_web.ex", "../lib/*_web/**/*.*ex"],
|
7 |
+
theme: {
|
8 |
+
extend: {
|
9 |
+
colors: {
|
10 |
+
brand: "#FD4F00",
|
11 |
+
},
|
12 |
+
},
|
13 |
+
},
|
14 |
+
plugins: [
|
15 |
+
require("@tailwindcss/forms"),
|
16 |
+
plugin(({ addVariant }) =>
|
17 |
+
addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])
|
18 |
+
),
|
19 |
+
plugin(({ addVariant }) =>
|
20 |
+
addVariant("phx-click-loading", [
|
21 |
+
".phx-click-loading&",
|
22 |
+
".phx-click-loading &",
|
23 |
+
])
|
24 |
+
),
|
25 |
+
plugin(({ addVariant }) =>
|
26 |
+
addVariant("phx-submit-loading", [
|
27 |
+
".phx-submit-loading&",
|
28 |
+
".phx-submit-loading &",
|
29 |
+
])
|
30 |
+
),
|
31 |
+
plugin(({ addVariant }) =>
|
32 |
+
addVariant("phx-change-loading", [
|
33 |
+
".phx-change-loading&",
|
34 |
+
".phx-change-loading &",
|
35 |
+
])
|
36 |
+
),
|
37 |
+
|
38 |
+
plugin(function ({ matchComponents, theme }) {
|
39 |
+
let iconsDir = path.join(__dirname, "../priv/hero_icons/optimized");
|
40 |
+
let values = {};
|
41 |
+
let icons = [
|
42 |
+
["", "/24/outline"],
|
43 |
+
["-solid", "/24/solid"],
|
44 |
+
["-mini", "/20/solid"],
|
45 |
+
];
|
46 |
+
icons.forEach(([suffix, dir]) => {
|
47 |
+
fs.readdirSync(path.join(iconsDir, dir)).map((file) => {
|
48 |
+
let name = path.basename(file, ".svg") + suffix;
|
49 |
+
values[name] = { name, fullPath: path.join(iconsDir, dir, file) };
|
50 |
+
});
|
51 |
+
});
|
52 |
+
matchComponents(
|
53 |
+
{
|
54 |
+
hero: ({ name, fullPath }) => {
|
55 |
+
let content = fs
|
56 |
+
.readFileSync(fullPath)
|
57 |
+
.toString()
|
58 |
+
.replace(/\r?\n|\r/g, "");
|
59 |
+
return {
|
60 |
+
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
|
61 |
+
"-webkit-mask": `var(--hero-${name})`,
|
62 |
+
mask: `var(--hero-${name})`,
|
63 |
+
"background-color": "currentColor",
|
64 |
+
"vertical-align": "middle",
|
65 |
+
display: "inline-block",
|
66 |
+
width: theme("spacing.5"),
|
67 |
+
height: theme("spacing.5"),
|
68 |
+
};
|
69 |
+
},
|
70 |
+
},
|
71 |
+
{ values }
|
72 |
+
);
|
73 |
+
}),
|
74 |
+
],
|
75 |
+
};
|
assets/vendor/topbar.js
ADDED
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* @license MIT
|
3 |
+
* topbar 2.0.0, 2023-02-04
|
4 |
+
* https://buunguyen.github.io/topbar
|
5 |
+
* Copyright (c) 2021 Buu Nguyen
|
6 |
+
*/
|
7 |
+
(function (window, document) {
|
8 |
+
"use strict";
|
9 |
+
|
10 |
+
// https://gist.github.com/paulirish/1579671
|
11 |
+
(function () {
|
12 |
+
var lastTime = 0;
|
13 |
+
var vendors = ["ms", "moz", "webkit", "o"];
|
14 |
+
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
15 |
+
window.requestAnimationFrame =
|
16 |
+
window[vendors[x] + "RequestAnimationFrame"];
|
17 |
+
window.cancelAnimationFrame =
|
18 |
+
window[vendors[x] + "CancelAnimationFrame"] ||
|
19 |
+
window[vendors[x] + "CancelRequestAnimationFrame"];
|
20 |
+
}
|
21 |
+
if (!window.requestAnimationFrame)
|
22 |
+
window.requestAnimationFrame = function (callback, element) {
|
23 |
+
var currTime = new Date().getTime();
|
24 |
+
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
25 |
+
var id = window.setTimeout(function () {
|
26 |
+
callback(currTime + timeToCall);
|
27 |
+
}, timeToCall);
|
28 |
+
lastTime = currTime + timeToCall;
|
29 |
+
return id;
|
30 |
+
};
|
31 |
+
if (!window.cancelAnimationFrame)
|
32 |
+
window.cancelAnimationFrame = function (id) {
|
33 |
+
clearTimeout(id);
|
34 |
+
};
|
35 |
+
})();
|
36 |
+
|
37 |
+
var canvas,
|
38 |
+
currentProgress,
|
39 |
+
showing,
|
40 |
+
progressTimerId = null,
|
41 |
+
fadeTimerId = null,
|
42 |
+
delayTimerId = null,
|
43 |
+
addEvent = function (elem, type, handler) {
|
44 |
+
if (elem.addEventListener) elem.addEventListener(type, handler, false);
|
45 |
+
else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
|
46 |
+
else elem["on" + type] = handler;
|
47 |
+
},
|
48 |
+
options = {
|
49 |
+
autoRun: true,
|
50 |
+
barThickness: 3,
|
51 |
+
barColors: {
|
52 |
+
0: "rgba(26, 188, 156, .9)",
|
53 |
+
".25": "rgba(52, 152, 219, .9)",
|
54 |
+
".50": "rgba(241, 196, 15, .9)",
|
55 |
+
".75": "rgba(230, 126, 34, .9)",
|
56 |
+
"1.0": "rgba(211, 84, 0, .9)",
|
57 |
+
},
|
58 |
+
shadowBlur: 10,
|
59 |
+
shadowColor: "rgba(0, 0, 0, .6)",
|
60 |
+
className: null,
|
61 |
+
},
|
62 |
+
repaint = function () {
|
63 |
+
canvas.width = window.innerWidth;
|
64 |
+
canvas.height = options.barThickness * 5; // need space for shadow
|
65 |
+
|
66 |
+
var ctx = canvas.getContext("2d");
|
67 |
+
ctx.shadowBlur = options.shadowBlur;
|
68 |
+
ctx.shadowColor = options.shadowColor;
|
69 |
+
|
70 |
+
var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
|
71 |
+
for (var stop in options.barColors)
|
72 |
+
lineGradient.addColorStop(stop, options.barColors[stop]);
|
73 |
+
ctx.lineWidth = options.barThickness;
|
74 |
+
ctx.beginPath();
|
75 |
+
ctx.moveTo(0, options.barThickness / 2);
|
76 |
+
ctx.lineTo(
|
77 |
+
Math.ceil(currentProgress * canvas.width),
|
78 |
+
options.barThickness / 2
|
79 |
+
);
|
80 |
+
ctx.strokeStyle = lineGradient;
|
81 |
+
ctx.stroke();
|
82 |
+
},
|
83 |
+
createCanvas = function () {
|
84 |
+
canvas = document.createElement("canvas");
|
85 |
+
var style = canvas.style;
|
86 |
+
style.position = "fixed";
|
87 |
+
style.top = style.left = style.right = style.margin = style.padding = 0;
|
88 |
+
style.zIndex = 100001;
|
89 |
+
style.display = "none";
|
90 |
+
if (options.className) canvas.classList.add(options.className);
|
91 |
+
document.body.appendChild(canvas);
|
92 |
+
addEvent(window, "resize", repaint);
|
93 |
+
},
|
94 |
+
topbar = {
|
95 |
+
config: function (opts) {
|
96 |
+
for (var key in opts)
|
97 |
+
if (options.hasOwnProperty(key)) options[key] = opts[key];
|
98 |
+
},
|
99 |
+
show: function (delay) {
|
100 |
+
if (showing) return;
|
101 |
+
if (delay) {
|
102 |
+
if (delayTimerId) return;
|
103 |
+
delayTimerId = setTimeout(() => topbar.show(), delay);
|
104 |
+
} else {
|
105 |
+
showing = true;
|
106 |
+
if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
|
107 |
+
if (!canvas) createCanvas();
|
108 |
+
canvas.style.opacity = 1;
|
109 |
+
canvas.style.display = "block";
|
110 |
+
topbar.progress(0);
|
111 |
+
if (options.autoRun) {
|
112 |
+
(function loop() {
|
113 |
+
progressTimerId = window.requestAnimationFrame(loop);
|
114 |
+
topbar.progress(
|
115 |
+
"+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
|
116 |
+
);
|
117 |
+
})();
|
118 |
+
}
|
119 |
+
}
|
120 |
+
},
|
121 |
+
progress: function (to) {
|
122 |
+
if (typeof to === "undefined") return currentProgress;
|
123 |
+
if (typeof to === "string") {
|
124 |
+
to =
|
125 |
+
(to.indexOf("+") >= 0 || to.indexOf("-") >= 0
|
126 |
+
? currentProgress
|
127 |
+
: 0) + parseFloat(to);
|
128 |
+
}
|
129 |
+
currentProgress = to > 1 ? 1 : to;
|
130 |
+
repaint();
|
131 |
+
return currentProgress;
|
132 |
+
},
|
133 |
+
hide: function () {
|
134 |
+
clearTimeout(delayTimerId);
|
135 |
+
delayTimerId = null;
|
136 |
+
if (!showing) return;
|
137 |
+
showing = false;
|
138 |
+
if (progressTimerId != null) {
|
139 |
+
window.cancelAnimationFrame(progressTimerId);
|
140 |
+
progressTimerId = null;
|
141 |
+
}
|
142 |
+
(function loop() {
|
143 |
+
if (topbar.progress("+.1") >= 1) {
|
144 |
+
canvas.style.opacity -= 0.05;
|
145 |
+
if (canvas.style.opacity <= 0.05) {
|
146 |
+
canvas.style.display = "none";
|
147 |
+
fadeTimerId = null;
|
148 |
+
return;
|
149 |
+
}
|
150 |
+
}
|
151 |
+
fadeTimerId = window.requestAnimationFrame(loop);
|
152 |
+
})();
|
153 |
+
},
|
154 |
+
};
|
155 |
+
|
156 |
+
if (typeof module === "object" && typeof module.exports === "object") {
|
157 |
+
module.exports = topbar;
|
158 |
+
} else if (typeof define === "function" && define.amd) {
|
159 |
+
define(function () {
|
160 |
+
return topbar;
|
161 |
+
});
|
162 |
+
} else {
|
163 |
+
this.topbar = topbar;
|
164 |
+
}
|
165 |
+
}.call(this, window, document));
|
config/config.exs
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Config
|
2 |
+
|
3 |
+
# Configures the endpoint
|
4 |
+
config :chai, ChaiWeb.Endpoint,
|
5 |
+
url: [host: "localhost"],
|
6 |
+
render_errors: [
|
7 |
+
formats: [html: ChaiWeb.ErrorHTML],
|
8 |
+
layout: false
|
9 |
+
],
|
10 |
+
pubsub_server: Chai.PubSub,
|
11 |
+
live_view: [signing_salt: "HhjjaWHp"]
|
12 |
+
|
13 |
+
# Configure esbuild (the version is required)
|
14 |
+
config :esbuild,
|
15 |
+
version: "0.14.41",
|
16 |
+
default: [
|
17 |
+
args:
|
18 |
+
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
|
19 |
+
cd: Path.expand("../assets", __DIR__),
|
20 |
+
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
|
21 |
+
]
|
22 |
+
|
23 |
+
# Configure tailwind (the version is required)
|
24 |
+
config :tailwind,
|
25 |
+
version: "3.2.4",
|
26 |
+
default: [
|
27 |
+
args: ~w(
|
28 |
+
--config=tailwind.config.js
|
29 |
+
--input=css/app.css
|
30 |
+
--output=../priv/static/assets/app.css
|
31 |
+
),
|
32 |
+
cd: Path.expand("../assets", __DIR__)
|
33 |
+
]
|
34 |
+
|
35 |
+
# Configures Elixir's Logger
|
36 |
+
config :logger, :console,
|
37 |
+
format: "$time $metadata[$level] $message\n",
|
38 |
+
metadata: [:request_id]
|
39 |
+
|
40 |
+
# Use Jason for JSON parsing in Phoenix
|
41 |
+
config :phoenix, :json_library, Jason
|
42 |
+
|
43 |
+
# Import environment specific config. This must remain at the bottom
|
44 |
+
# of this file so it overrides the configuration defined above.
|
45 |
+
import_config "#{config_env()}.exs"
|
config/dev.exs
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Config
|
2 |
+
|
3 |
+
# For development, we disable any cache and enable
|
4 |
+
# debugging and code reloading.
|
5 |
+
config :chai, ChaiWeb.Endpoint,
|
6 |
+
# Binding to loopback ipv4 address prevents access from other machines.
|
7 |
+
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
|
8 |
+
http: [ip: {127, 0, 0, 1}, port: 4040],
|
9 |
+
check_origin: false,
|
10 |
+
code_reloader: true,
|
11 |
+
debug_errors: true,
|
12 |
+
secret_key_base: "POQcb0mcoX3qbIKsRel3LlM4dvpDOssogUkyUN9Tyt4TPLgtQWKzDnjxxedUfcxC",
|
13 |
+
watchers: [
|
14 |
+
esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
|
15 |
+
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
|
16 |
+
]
|
17 |
+
|
18 |
+
# Watch static and templates for browser reloading.
|
19 |
+
config :chai, ChaiWeb.Endpoint,
|
20 |
+
live_reload: [
|
21 |
+
patterns: [
|
22 |
+
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
|
23 |
+
~r"lib/chai_web/(controllers|live|components)/.*(ex|heex)$"
|
24 |
+
]
|
25 |
+
]
|
26 |
+
|
27 |
+
# Enable dev routes for dashboard and mailbox
|
28 |
+
config :chai, dev_routes: true
|
29 |
+
|
30 |
+
# Do not include metadata nor timestamps in development logs
|
31 |
+
config :logger, :console, format: "[$level] $message\n"
|
32 |
+
|
33 |
+
# Set a higher stacktrace during development. Avoid configuring such
|
34 |
+
# in production as building large stacktraces may be expensive.
|
35 |
+
config :phoenix, :stacktrace_depth, 20
|
36 |
+
|
37 |
+
# Initialize plugs at runtime for faster development compilation
|
38 |
+
config :phoenix, :plug_init_mode, :runtime
|
config/prod.exs
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Config
|
2 |
+
|
3 |
+
# For production, don't forget to configure the url host
|
4 |
+
# to something meaningful, Phoenix uses this information
|
5 |
+
# when generating URLs.
|
6 |
+
|
7 |
+
# Note we also include the path to a cache manifest
|
8 |
+
# containing the digested version of static files. This
|
9 |
+
# manifest is generated by the `mix phx.digest` task,
|
10 |
+
# which you should run after static files are built and
|
11 |
+
# before starting your production server.
|
12 |
+
config :chai, ChaiWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
|
13 |
+
|
14 |
+
# Do not print debug messages in production
|
15 |
+
config :logger, level: :info
|
16 |
+
|
17 |
+
# Runtime production configuration, including reading
|
18 |
+
# of environment variables, is done on config/runtime.exs.
|
config/runtime.exs
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Config
|
2 |
+
|
3 |
+
if System.get_env("PHX_SERVER") do
|
4 |
+
config :chai, ChaiWeb.Endpoint, server: true
|
5 |
+
end
|
6 |
+
|
7 |
+
if config_env() == :prod do
|
8 |
+
secret_key_base =
|
9 |
+
System.get_env("SECRET_KEY_BASE") ||
|
10 |
+
raise """
|
11 |
+
environment variable SECRET_KEY_BASE is missing.
|
12 |
+
You can generate one by calling: mix phx.gen.secret
|
13 |
+
"""
|
14 |
+
|
15 |
+
host = System.get_env("PHX_HOST") || "example.com"
|
16 |
+
port = String.to_integer(System.get_env("PORT") || "4000")
|
17 |
+
|
18 |
+
config :chai, ChaiWeb.Endpoint,
|
19 |
+
url: [host: host, port: 443, scheme: "https"],
|
20 |
+
http: [
|
21 |
+
ip: {0, 0, 0, 0, 0, 0, 0, 0},
|
22 |
+
port: port
|
23 |
+
],
|
24 |
+
secret_key_base: secret_key_base
|
25 |
+
end
|
config/test.exs
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Config
|
2 |
+
|
3 |
+
# We don't run a server during test. If one is required,
|
4 |
+
# you can enable the server option below.
|
5 |
+
config :chai, ChaiWeb.Endpoint,
|
6 |
+
http: [ip: {127, 0, 0, 1}, port: 4002],
|
7 |
+
secret_key_base: "kKQJBTFBlnw1PnHtYTDG3G9sfIuXM3DxJLCyhmxnfyJ9wdAwOdRfthMTQR+Xa4ln",
|
8 |
+
server: false
|
9 |
+
|
10 |
+
# Print only warnings and errors during test
|
11 |
+
config :logger, level: :warning
|
12 |
+
|
13 |
+
# Initialize plugs at runtime for faster test compilation
|
14 |
+
config :phoenix, :plug_init_mode, :runtime
|
lib/chai.ex
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
defmodule Chai do
|
2 |
+
end
|
lib/chai/application.ex
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule Chai.Application do
|
2 |
+
@moduledoc false
|
3 |
+
|
4 |
+
use Application
|
5 |
+
|
6 |
+
@impl true
|
7 |
+
def start(_type, _args) do
|
8 |
+
children = [
|
9 |
+
# Start the Telemetry supervisor
|
10 |
+
ChaiWeb.Telemetry,
|
11 |
+
# Start the PubSub system
|
12 |
+
{Phoenix.PubSub, name: Chai.PubSub},
|
13 |
+
# Start the Endpoint (http/https)
|
14 |
+
ChaiWeb.Endpoint
|
15 |
+
]
|
16 |
+
|
17 |
+
opts = [strategy: :one_for_one, name: Chai.Supervisor]
|
18 |
+
Supervisor.start_link(children, opts)
|
19 |
+
end
|
20 |
+
|
21 |
+
# Tell Phoenix to update the endpoint configuration
|
22 |
+
# whenever the application is updated.
|
23 |
+
@impl true
|
24 |
+
def config_change(changed, _new, removed) do
|
25 |
+
ChaiWeb.Endpoint.config_change(changed, removed)
|
26 |
+
:ok
|
27 |
+
end
|
28 |
+
end
|
lib/chai_web.ex
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule ChaiWeb do
|
2 |
+
@moduledoc """
|
3 |
+
The entrypoint for defining your web interface, such
|
4 |
+
as controllers, components, channels, and so on.
|
5 |
+
|
6 |
+
This can be used in your application as:
|
7 |
+
|
8 |
+
use ChaiWeb, :controller
|
9 |
+
use ChaiWeb, :html
|
10 |
+
|
11 |
+
The definitions below will be executed for every controller,
|
12 |
+
component, etc, so keep them short and clean, focused
|
13 |
+
on imports, uses and aliases.
|
14 |
+
|
15 |
+
Do NOT define functions inside the quoted expressions
|
16 |
+
below. Instead, define additional modules and import
|
17 |
+
those modules here.
|
18 |
+
"""
|
19 |
+
|
20 |
+
def static_paths, do: ~w(assets fonts images favicon.svg logo.svg robots.txt)
|
21 |
+
|
22 |
+
def router do
|
23 |
+
quote do
|
24 |
+
use Phoenix.Router, helpers: false
|
25 |
+
|
26 |
+
# Import common connection and controller functions to use in pipelines
|
27 |
+
import Plug.Conn
|
28 |
+
import Phoenix.Controller
|
29 |
+
import Phoenix.LiveView.Router
|
30 |
+
end
|
31 |
+
end
|
32 |
+
|
33 |
+
def channel do
|
34 |
+
quote do
|
35 |
+
use Phoenix.Channel
|
36 |
+
end
|
37 |
+
end
|
38 |
+
|
39 |
+
def controller do
|
40 |
+
quote do
|
41 |
+
use Phoenix.Controller,
|
42 |
+
formats: [:html, :json],
|
43 |
+
layouts: [html: ChaiWeb.Layouts]
|
44 |
+
|
45 |
+
import Plug.Conn
|
46 |
+
|
47 |
+
unquote(verified_routes())
|
48 |
+
end
|
49 |
+
end
|
50 |
+
|
51 |
+
def live_view do
|
52 |
+
quote do
|
53 |
+
use Phoenix.LiveView,
|
54 |
+
layout: {ChaiWeb.Layouts, :app}
|
55 |
+
|
56 |
+
unquote(html_helpers())
|
57 |
+
end
|
58 |
+
end
|
59 |
+
|
60 |
+
def live_component do
|
61 |
+
quote do
|
62 |
+
use Phoenix.LiveComponent
|
63 |
+
|
64 |
+
unquote(html_helpers())
|
65 |
+
end
|
66 |
+
end
|
67 |
+
|
68 |
+
def html do
|
69 |
+
quote do
|
70 |
+
use Phoenix.Component
|
71 |
+
|
72 |
+
# Import convenience functions from controllers
|
73 |
+
import Phoenix.Controller,
|
74 |
+
only: [get_csrf_token: 0, view_module: 1, view_template: 1]
|
75 |
+
|
76 |
+
# Include general helpers for rendering HTML
|
77 |
+
unquote(html_helpers())
|
78 |
+
end
|
79 |
+
end
|
80 |
+
|
81 |
+
defp html_helpers do
|
82 |
+
quote do
|
83 |
+
# HTML escaping functionality
|
84 |
+
import Phoenix.HTML
|
85 |
+
# Core UI components and translation
|
86 |
+
import ChaiWeb.CoreComponents
|
87 |
+
|
88 |
+
# Shortcut for generating JS commands
|
89 |
+
alias Phoenix.LiveView.JS
|
90 |
+
|
91 |
+
# Routes generation with the ~p sigil
|
92 |
+
unquote(verified_routes())
|
93 |
+
end
|
94 |
+
end
|
95 |
+
|
96 |
+
def verified_routes do
|
97 |
+
quote do
|
98 |
+
use Phoenix.VerifiedRoutes,
|
99 |
+
endpoint: ChaiWeb.Endpoint,
|
100 |
+
router: ChaiWeb.Router,
|
101 |
+
statics: ChaiWeb.static_paths()
|
102 |
+
end
|
103 |
+
end
|
104 |
+
|
105 |
+
@doc """
|
106 |
+
When used, dispatch to the appropriate controller/view/etc.
|
107 |
+
"""
|
108 |
+
defmacro __using__(which) when is_atom(which) do
|
109 |
+
apply(__MODULE__, which, [])
|
110 |
+
end
|
111 |
+
end
|
lib/chai_web/components/core_components.ex
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule ChaiWeb.CoreComponents do
|
2 |
+
@moduledoc """
|
3 |
+
Provides core UI components.
|
4 |
+
"""
|
5 |
+
|
6 |
+
use Phoenix.Component
|
7 |
+
|
8 |
+
alias Phoenix.LiveView.JS
|
9 |
+
|
10 |
+
@doc """
|
11 |
+
Renders flash notices.
|
12 |
+
|
13 |
+
## Examples
|
14 |
+
|
15 |
+
<.flash kind={:info} flash={@flash} />
|
16 |
+
<.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!</.flash>
|
17 |
+
|
18 |
+
"""
|
19 |
+
attr :id, :string, default: "flash", doc: "the optional id of flash container"
|
20 |
+
attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
|
21 |
+
attr :title, :string, default: nil
|
22 |
+
attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
|
23 |
+
attr :autoshow, :boolean, default: true, doc: "whether to auto show the flash on mount"
|
24 |
+
attr :close, :boolean, default: true, doc: "whether the flash can be closed"
|
25 |
+
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
|
26 |
+
|
27 |
+
slot :inner_block, doc: "the optional inner block that renders the flash message"
|
28 |
+
|
29 |
+
def flash(assigns) do
|
30 |
+
~H"""
|
31 |
+
<div
|
32 |
+
:if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
|
33 |
+
id={@id}
|
34 |
+
phx-mounted={@autoshow && show("##{@id}")}
|
35 |
+
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
|
36 |
+
role="alert"
|
37 |
+
class={[
|
38 |
+
"fixed hidden top-2 right-2 w-80 sm:w-96 z-50 rounded-lg p-3 shadow-md shadow-zinc-900/5 ring-1",
|
39 |
+
@kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
|
40 |
+
@kind == :error && "bg-rose-50 p-3 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
|
41 |
+
]}
|
42 |
+
{@rest}
|
43 |
+
>
|
44 |
+
<p :if={@title} class="flex items-center gap-1.5 text-[0.8125rem] font-semibold leading-6">
|
45 |
+
<.icon :if={@kind == :info} name="hero-information-circle-mini" class="w-4 h-4" />
|
46 |
+
<.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="w-4 h-4" />
|
47 |
+
<%= @title %>
|
48 |
+
</p>
|
49 |
+
<p class="mt-2 text-[0.8125rem] leading-5"><%= msg %></p>
|
50 |
+
<button :if={@close} type="button" class="group absolute top-2 right-1 p-2" aria-label="close">
|
51 |
+
<.icon name="hero-x-mark-solid" class="w-5 h-5 opacity-40 group-hover:opacity-70" />
|
52 |
+
</button>
|
53 |
+
</div>
|
54 |
+
"""
|
55 |
+
end
|
56 |
+
|
57 |
+
@doc """
|
58 |
+
Shows the flash group with standard titles and content.
|
59 |
+
|
60 |
+
## Examples
|
61 |
+
|
62 |
+
<.flash_group flash={@flash} />
|
63 |
+
|
64 |
+
"""
|
65 |
+
attr :flash, :map, required: true, doc: "the map of flash messages"
|
66 |
+
|
67 |
+
def flash_group(assigns) do
|
68 |
+
~H"""
|
69 |
+
<.flash kind={:info} title="Success!" flash={@flash} />
|
70 |
+
<.flash kind={:error} title="Error!" flash={@flash} />
|
71 |
+
<.flash
|
72 |
+
id="disconnected"
|
73 |
+
kind={:error}
|
74 |
+
title="We can't find the internet"
|
75 |
+
close={false}
|
76 |
+
autoshow={false}
|
77 |
+
phx-disconnected={show("#disconnected")}
|
78 |
+
phx-connected={hide("#disconnected")}
|
79 |
+
>
|
80 |
+
Attempting to reconnect <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" />
|
81 |
+
</.flash>
|
82 |
+
"""
|
83 |
+
end
|
84 |
+
|
85 |
+
@doc """
|
86 |
+
Renders a [Hero Icon](https://heroicons.com).
|
87 |
+
|
88 |
+
Hero icons come in three styles – outline, solid, and mini.
|
89 |
+
By default, the outline style is used, but solid an mini may
|
90 |
+
be applied by using the `-solid` and `-mini` suffix.
|
91 |
+
|
92 |
+
You can customize the size and colors of the icons by setting
|
93 |
+
width, height, and background color classes.
|
94 |
+
|
95 |
+
Icons are extracted from your `priv/hero_icons` directory and bundled
|
96 |
+
within your compiled app.css by the plugin in your `assets/tailwind.config.js`.
|
97 |
+
|
98 |
+
## Examples
|
99 |
+
|
100 |
+
<.icon name="hero-cake" />
|
101 |
+
<.icon name="hero-cake-solid" />
|
102 |
+
<.icon name="hero-cake-mini" />
|
103 |
+
<.icon name="hero-bolt" class="bg-blue-500 w-10 h-10" />
|
104 |
+
|
105 |
+
"""
|
106 |
+
attr :name, :string, required: true
|
107 |
+
attr :class, :string, default: nil
|
108 |
+
|
109 |
+
def icon(%{name: "hero-" <> _} = assigns) do
|
110 |
+
~H"""
|
111 |
+
<span class={[@name, @class]} />
|
112 |
+
"""
|
113 |
+
end
|
114 |
+
|
115 |
+
## JS Commands
|
116 |
+
|
117 |
+
def show(js \\ %JS{}, selector) do
|
118 |
+
JS.show(js,
|
119 |
+
to: selector,
|
120 |
+
transition:
|
121 |
+
{"transition-all transform ease-out duration-300",
|
122 |
+
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
|
123 |
+
"opacity-100 translate-y-0 sm:scale-100"}
|
124 |
+
)
|
125 |
+
end
|
126 |
+
|
127 |
+
def hide(js \\ %JS{}, selector) do
|
128 |
+
JS.hide(js,
|
129 |
+
to: selector,
|
130 |
+
time: 200,
|
131 |
+
transition:
|
132 |
+
{"transition-all transform ease-in duration-200",
|
133 |
+
"opacity-100 translate-y-0 sm:scale-100",
|
134 |
+
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
|
135 |
+
)
|
136 |
+
end
|
137 |
+
end
|
lib/chai_web/components/layouts.ex
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule ChaiWeb.Layouts do
|
2 |
+
use ChaiWeb, :html
|
3 |
+
|
4 |
+
embed_templates "layouts/*"
|
5 |
+
end
|
lib/chai_web/components/layouts/app.html.heex
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<header class="px-4 sm:px-6 lg:px-8">
|
2 |
+
<div class="flex items-center justify-between border-b border-zinc-100 py-3">
|
3 |
+
<div class="flex items-center gap-4">
|
4 |
+
<a href={~p"/"}>
|
5 |
+
<img src={~s"/logo.svg"} class="h-8" />
|
6 |
+
</a>
|
7 |
+
</div>
|
8 |
+
<div class="flex items-center gap-4">
|
9 |
+
<a
|
10 |
+
href="https://huggingface.co/spaces/jonatanklosko/chai/tree/main"
|
11 |
+
target="_blank"
|
12 |
+
class="text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
|
13 |
+
>
|
14 |
+
Source
|
15 |
+
</a>
|
16 |
+
<a
|
17 |
+
href="https://github.com/elixir-nx/bumblebee"
|
18 |
+
target="_blank"
|
19 |
+
class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
|
20 |
+
>
|
21 |
+
Learn more <span aria-hidden="true">→</span>
|
22 |
+
</a>
|
23 |
+
</div>
|
24 |
+
</div>
|
25 |
+
</header>
|
26 |
+
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
27 |
+
<div class="mx-auto max-w-2xl">
|
28 |
+
<.flash_group flash={@flash} />
|
29 |
+
<%= @inner_content %>
|
30 |
+
</div>
|
31 |
+
</main>
|
lib/chai_web/components/layouts/root.html.heex
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en" style="scrollbar-gutter: stable;">
|
3 |
+
<head>
|
4 |
+
<meta charset="utf-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
6 |
+
<meta name="csrf-token" content={get_csrf_token()} />
|
7 |
+
<link rel="icon" type="image/svg+xml" href={~p"/favicon.svg"} />
|
8 |
+
<.live_title>
|
9 |
+
<%= assigns[:page_title] || "Chai" %>
|
10 |
+
</.live_title>
|
11 |
+
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
12 |
+
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
13 |
+
</script>
|
14 |
+
</head>
|
15 |
+
<body class="bg-white antialiased">
|
16 |
+
<%= @inner_content %>
|
17 |
+
</body>
|
18 |
+
</html>
|
lib/chai_web/controllers/error_html.ex
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule ChaiWeb.ErrorHTML do
|
2 |
+
use ChaiWeb, :html
|
3 |
+
|
4 |
+
def render(template, _assigns) do
|
5 |
+
Phoenix.Controller.status_message_from_template(template)
|
6 |
+
end
|
7 |
+
end
|
lib/chai_web/endpoint.ex
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule ChaiWeb.Endpoint do
|
2 |
+
use Phoenix.Endpoint, otp_app: :chai
|
3 |
+
|
4 |
+
# The session will be stored in the cookie and signed,
|
5 |
+
# this means its contents can be read but not tampered with.
|
6 |
+
# Set :encryption_salt if you would also like to encrypt it.
|
7 |
+
@session_options [
|
8 |
+
store: :cookie,
|
9 |
+
key: "_chai_key",
|
10 |
+
signing_salt: "HnJ/u6tl",
|
11 |
+
same_site: "Lax"
|
12 |
+
]
|
13 |
+
|
14 |
+
socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
|
15 |
+
|
16 |
+
plug Plug.Static,
|
17 |
+
at: "/",
|
18 |
+
from: :chai,
|
19 |
+
gzip: false,
|
20 |
+
only: ChaiWeb.static_paths()
|
21 |
+
|
22 |
+
if code_reloading? do
|
23 |
+
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
|
24 |
+
plug Phoenix.LiveReloader
|
25 |
+
plug Phoenix.CodeReloader
|
26 |
+
end
|
27 |
+
|
28 |
+
plug Phoenix.LiveDashboard.RequestLogger,
|
29 |
+
param_key: "request_logger",
|
30 |
+
cookie_key: "request_logger"
|
31 |
+
|
32 |
+
plug Plug.RequestId
|
33 |
+
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
|
34 |
+
|
35 |
+
plug Plug.Parsers,
|
36 |
+
parsers: [:urlencoded, :multipart, :json],
|
37 |
+
pass: ["*/*"],
|
38 |
+
json_decoder: Phoenix.json_library()
|
39 |
+
|
40 |
+
plug Plug.MethodOverride
|
41 |
+
plug Plug.Head
|
42 |
+
plug Plug.Session, @session_options
|
43 |
+
plug ChaiWeb.Router
|
44 |
+
end
|
lib/chai_web/router.ex
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule ChaiWeb.Router do
|
2 |
+
use ChaiWeb, :router
|
3 |
+
|
4 |
+
pipeline :browser do
|
5 |
+
plug :accepts, ["html"]
|
6 |
+
plug :fetch_session
|
7 |
+
plug :fetch_live_flash
|
8 |
+
plug :put_root_layout, {ChaiWeb.Layouts, :root}
|
9 |
+
plug :protect_from_forgery
|
10 |
+
plug :put_secure_browser_headers
|
11 |
+
end
|
12 |
+
|
13 |
+
pipeline :api do
|
14 |
+
plug :accepts, ["json"]
|
15 |
+
end
|
16 |
+
|
17 |
+
scope "/", ChaiWeb do
|
18 |
+
pipe_through :browser
|
19 |
+
end
|
20 |
+
|
21 |
+
# Enable LiveDashboard in development
|
22 |
+
if Application.compile_env(:chai, :dev_routes) do
|
23 |
+
import Phoenix.LiveDashboard.Router
|
24 |
+
|
25 |
+
scope "/dev" do
|
26 |
+
pipe_through :browser
|
27 |
+
|
28 |
+
live_dashboard "/dashboard", metrics: ChaiWeb.Telemetry
|
29 |
+
end
|
30 |
+
end
|
31 |
+
end
|
lib/chai_web/telemetry.ex
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule ChaiWeb.Telemetry do
|
2 |
+
use Supervisor
|
3 |
+
import Telemetry.Metrics
|
4 |
+
|
5 |
+
def start_link(arg) do
|
6 |
+
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
7 |
+
end
|
8 |
+
|
9 |
+
@impl true
|
10 |
+
def init(_arg) do
|
11 |
+
children = [
|
12 |
+
# Telemetry poller will execute the given period measurements
|
13 |
+
# every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
|
14 |
+
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
|
15 |
+
# Add reporters as children of your supervision tree.
|
16 |
+
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
|
17 |
+
]
|
18 |
+
|
19 |
+
Supervisor.init(children, strategy: :one_for_one)
|
20 |
+
end
|
21 |
+
|
22 |
+
def metrics do
|
23 |
+
[
|
24 |
+
# Phoenix Metrics
|
25 |
+
summary("phoenix.endpoint.start.system_time",
|
26 |
+
unit: {:native, :millisecond}
|
27 |
+
),
|
28 |
+
summary("phoenix.endpoint.stop.duration",
|
29 |
+
unit: {:native, :millisecond}
|
30 |
+
),
|
31 |
+
summary("phoenix.router_dispatch.start.system_time",
|
32 |
+
tags: [:route],
|
33 |
+
unit: {:native, :millisecond}
|
34 |
+
),
|
35 |
+
summary("phoenix.router_dispatch.exception.duration",
|
36 |
+
tags: [:route],
|
37 |
+
unit: {:native, :millisecond}
|
38 |
+
),
|
39 |
+
summary("phoenix.router_dispatch.stop.duration",
|
40 |
+
tags: [:route],
|
41 |
+
unit: {:native, :millisecond}
|
42 |
+
),
|
43 |
+
summary("phoenix.socket_connected.duration",
|
44 |
+
unit: {:native, :millisecond}
|
45 |
+
),
|
46 |
+
summary("phoenix.channel_join.duration",
|
47 |
+
unit: {:native, :millisecond}
|
48 |
+
),
|
49 |
+
summary("phoenix.channel_handled_in.duration",
|
50 |
+
tags: [:event],
|
51 |
+
unit: {:native, :millisecond}
|
52 |
+
),
|
53 |
+
|
54 |
+
# VM Metrics
|
55 |
+
summary("vm.memory.total", unit: {:byte, :kilobyte}),
|
56 |
+
summary("vm.total_run_queue_lengths.total"),
|
57 |
+
summary("vm.total_run_queue_lengths.cpu"),
|
58 |
+
summary("vm.total_run_queue_lengths.io")
|
59 |
+
]
|
60 |
+
end
|
61 |
+
|
62 |
+
defp periodic_measurements do
|
63 |
+
[
|
64 |
+
# A module, function and arguments to be invoked periodically.
|
65 |
+
# This function must call :telemetry.execute/3 and a metric must be added above.
|
66 |
+
# {ChaiWeb, :count_users, []}
|
67 |
+
]
|
68 |
+
end
|
69 |
+
end
|
mix.exs
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule Chai.MixProject do
|
2 |
+
use Mix.Project
|
3 |
+
|
4 |
+
def project do
|
5 |
+
[
|
6 |
+
app: :chai,
|
7 |
+
version: "0.1.0",
|
8 |
+
elixir: "~> 1.14",
|
9 |
+
elixirc_paths: elixirc_paths(Mix.env()),
|
10 |
+
start_permanent: Mix.env() == :prod,
|
11 |
+
aliases: aliases(),
|
12 |
+
deps: deps()
|
13 |
+
]
|
14 |
+
end
|
15 |
+
|
16 |
+
def application do
|
17 |
+
[
|
18 |
+
mod: {Chai.Application, []},
|
19 |
+
extra_applications: [:logger, :runtime_tools]
|
20 |
+
]
|
21 |
+
end
|
22 |
+
|
23 |
+
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
24 |
+
defp elixirc_paths(_), do: ["lib"]
|
25 |
+
|
26 |
+
defp deps do
|
27 |
+
[
|
28 |
+
{:phoenix, "~> 1.7.1"},
|
29 |
+
{:phoenix_html, "~> 3.3"},
|
30 |
+
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
31 |
+
{:phoenix_live_view, "~> 0.18.16"},
|
32 |
+
{:floki, ">= 0.30.0", only: :test},
|
33 |
+
{:phoenix_live_dashboard, "~> 0.7.2"},
|
34 |
+
{:esbuild, "~> 0.5", runtime: Mix.env() == :dev},
|
35 |
+
{:tailwind, "~> 0.1.8", runtime: Mix.env() == :dev},
|
36 |
+
{:telemetry_metrics, "~> 0.6"},
|
37 |
+
{:telemetry_poller, "~> 1.0"},
|
38 |
+
{:jason, "~> 1.2"},
|
39 |
+
{:plug_cowboy, "~> 2.5"}
|
40 |
+
]
|
41 |
+
end
|
42 |
+
|
43 |
+
defp aliases do
|
44 |
+
[
|
45 |
+
setup: ["deps.get", "assets.setup", "assets.build"],
|
46 |
+
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
|
47 |
+
"assets.build": ["tailwind default", "esbuild default"],
|
48 |
+
"assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]
|
49 |
+
]
|
50 |
+
end
|
51 |
+
end
|
mix.lock
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
%{
|
2 |
+
"castore": {:hex, :castore, "1.0.1", "240b9edb4e9e94f8f56ab39d8d2d0a57f49e46c56aced8f873892df8ff64ff5a", [:mix], [], "hexpm", "b4951de93c224d44fac71614beabd88b71932d0b1dea80d2f80fb9044e01bbb3"},
|
3 |
+
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
4 |
+
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
5 |
+
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
6 |
+
"esbuild": {:hex, :esbuild, "0.7.0", "ce3afb13cd2c5fd63e13c0e2d0e0831487a97a7696cfa563707342bb825d122a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4ae9f4f237c5ebcb001390b8ada65a12fb2bb04f3fe3d1f1692b7a06fbfe8752"},
|
7 |
+
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
8 |
+
"floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"},
|
9 |
+
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
10 |
+
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
|
11 |
+
"phoenix": {:hex, :phoenix, "1.7.2", "c375ffb482beb4e3d20894f84dd7920442884f5f5b70b9f4528cbe0cedefec63", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1ebca94b32b4d0e097ab2444a9742ed8ff3361acad17365e4e6b2e79b4792159"},
|
12 |
+
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
|
13 |
+
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
14 |
+
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
|
15 |
+
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
|
16 |
+
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
|
17 |
+
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
|
18 |
+
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
|
19 |
+
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
|
20 |
+
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
21 |
+
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
22 |
+
"tailwind": {:hex, :tailwind, "0.1.10", "21ed80ae1f411f747ee513470578acaaa1d0eb40170005350c5b0b6d07e2d624", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "e0fc474dfa8ed7a4573851ac69c5fd3ca70fbb0a5bada574d1d657ebc6f2f1f1"},
|
23 |
+
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
24 |
+
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
25 |
+
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
26 |
+
"websock": {:hex, :websock, "0.5.0", "f6bbce90226121d62a0715bca7c986c5e43de0ccc9475d79c55381d1796368cc", [:mix], [], "hexpm", "b51ac706df8a7a48a2c622ee02d09d68be8c40418698ffa909d73ae207eb5fb8"},
|
27 |
+
"websock_adapter": {:hex, :websock_adapter, "0.5.0", "cea35d8bbf1a6964e32d4b02ceb561dfb769c04f16d60d743885587e7d2ca55b", [:mix], [{:bandit, "~> 0.6", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "16318b124effab8209b1eb7906c636374f623dc9511a8278ad09c083cea5bb83"},
|
28 |
+
}
|
priv/hero_icons/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2020 Refactoring UI Inc.
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
priv/hero_icons/UPGRADE.md
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
You are running heroicons v2.0.16. To upgrade in place, you can run the following command,
|
2 |
+
where your `HERO_VSN` export is your desired version:
|
3 |
+
|
4 |
+
export HERO_VSN="2.0.16" ; \
|
5 |
+
curl -L -o optimized.zip "https://github.com/tailwindlabs/heroicons/archive/refs/tags/v${HERO_VSN}.zip" ; \
|
6 |
+
tar --strip-components=1 -xvf optimized.zip heroicons-${HERO_VSN}/optimized ; \
|
7 |
+
rm optimized.zip
|
priv/hero_icons/optimized/20/solid/academic-cap.svg
ADDED
|
priv/hero_icons/optimized/20/solid/adjustments-horizontal.svg
ADDED
|
priv/hero_icons/optimized/20/solid/adjustments-vertical.svg
ADDED
|
priv/hero_icons/optimized/20/solid/archive-box-arrow-down.svg
ADDED
|
priv/hero_icons/optimized/20/solid/archive-box-x-mark.svg
ADDED
|
priv/hero_icons/optimized/20/solid/archive-box.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-down-circle.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-down-left.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-down-on-square-stack.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-down-on-square.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-down-right.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-down-tray.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-down.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-left-circle.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-left-on-rectangle.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-left.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-long-down.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-long-left.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-long-right.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-long-up.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-path-rounded-square.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-path.svg
ADDED
|
priv/hero_icons/optimized/20/solid/arrow-right-circle.svg
ADDED
|