initial bootstrap/navbar setup akin to bones73k implementation

This commit is contained in:
Adam Piontek 2021-03-29 11:27:44 -04:00
parent dfe805b811
commit 0a83f8f317
32 changed files with 6856 additions and 7865 deletions

7
.gitignore vendored
View file

@ -32,3 +32,10 @@ npm-debug.log
# we ignore priv/static. You may want to comment # we ignore priv/static. You may want to comment
# this depending on your deployment strategy. # this depending on your deployment strategy.
/priv/static/ /priv/static/
# for vscode elixir_ls extension files
/.elixir_ls
# dev
TODO.md

View file

@ -0,0 +1,35 @@
// Colors
// $primary: #662c91;
// $secondary: #ee6c4d;
// $success: #3f784c;
// $info: #3f84e5;
// $warning: #ffec51;
$light: $gray-200;
$dark: $gray-800;
// Create your own map
$custom-colors: (
"primary": $primary,
"secondary": $secondary,
"success": $success,
"info": $info,
"warning": $warning,
"danger": $danger,
"light": $light,
"dark": $dark,
"gray": $gray-500,
);
// $custom-colors: (
// "custom-color": #900
// );
// Merge the maps
$theme-colors: map-merge($theme-colors, $custom-colors);
// misc
$navbar-dark-color: rgba($white, 0.75);
$navbar-dark-hover-color: rgba($white, 0.9);
$navbar-dark-active-color: $white;
$navbar-dark-disabled-color: rgba($white, 0.45);
$navbar-dark-toggler-border-color: rgba($white, 0.3);

View file

@ -0,0 +1,21 @@
/* Bootstrap custom variable overrides */
// Typography
//
// Font, line-height, and color for body text, headings, and more.
// stylelint-disable value-keyword-case
$font-family-sans-serif: Lato, system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
$font-family-secondary: Righteous, system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
$font-family-monospace: "Fira Mono", SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace !important;
$font-family-code: "Fira Code", "Fira Mono", SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace !important;
// Features
$enable-shadows: true;
$enable-negative-margins: true;

50
assets/css/_bs-load.scss Normal file
View file

@ -0,0 +1,50 @@
/* Bootstrap custom variable overrides */
@import "bs-custom";
// Required || Configuration
@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
/* Bootstrap custom variable overrides */
@import "bs-colors";
// Required || Configuration -- CONTINUED
@import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/utilities";
// Optional || Layout & components
@import "../node_modules/bootstrap/scss/root";
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
@import "../node_modules/bootstrap/scss/images";
@import "../node_modules/bootstrap/scss/containers";
@import "../node_modules/bootstrap/scss/grid";
// @import "../node_modules/bootstrap/scss/tables";
@import "../node_modules/bootstrap/scss/forms";
@import "../node_modules/bootstrap/scss/buttons";
@import "../node_modules/bootstrap/scss/transitions";
// @import "../node_modules/bootstrap/scss/dropdown";
// @import "../node_modules/bootstrap/scss/button-group";
@import "../node_modules/bootstrap/scss/nav";
@import "../node_modules/bootstrap/scss/navbar";
@import "../node_modules/bootstrap/scss/card";
// @import "../node_modules/bootstrap/scss/accordion";
// @import "../node_modules/bootstrap/scss/breadcrumb";
// @import "../node_modules/bootstrap/scss/pagination";
// @import "../node_modules/bootstrap/scss/badge";
// @import "../node_modules/bootstrap/scss/alert";
// @import "../node_modules/bootstrap/scss/progress";
// @import "../node_modules/bootstrap/scss/list-group";
// @import "../node_modules/bootstrap/scss/close";
// @import "../node_modules/bootstrap/scss/toasts";
// @import "../node_modules/bootstrap/scss/modal";
// @import "../node_modules/bootstrap/scss/tooltip";
// @import "../node_modules/bootstrap/scss/popover";
// @import "../node_modules/bootstrap/scss/carousel";
// @import "../node_modules/bootstrap/scss/spinners";
// Helpers
@import "../node_modules/bootstrap/scss/helpers";
// Utilities
@import "../node_modules/bootstrap/scss/utilities/api";

26
assets/css/_fonts.scss Normal file
View file

@ -0,0 +1,26 @@
/* Fontsource Lato */
// @import "../node_modules/@fontsource/lato/100.css"; /* thin | normal */
// @import "../node_modules/@fontsource/lato/100-italic.css"; /* thin | italic */
@import "../node_modules/@fontsource/lato/300.css"; /* light | normal */
// @import "../node_modules/@fontsource/lato/300-italic.css"; /* light | italic */
@import "../node_modules/@fontsource/lato/400.css"; /* normal | normal */
// @import "../node_modules/@fontsource/lato/400-italic.css"; /* normal | italic */
@import "../node_modules/@fontsource/lato/700.css"; /* bold | normal */
// @import "../node_modules/@fontsource/lato/700-italic.css"; /* bold | italic */
// @import "../node_modules/@fontsource/lato/900.css"; /* black | normal */
// @import "../node_modules/@fontsource/lato/900-italic.css"; /* black | italic */
/* Fontsource Righteous */
@import "../node_modules/@fontsource/righteous/400.css"; /* normal | normal */
/* Fontsource Fira Mono */
@import "../node_modules/@fontsource/fira-mono/400.css"; /* normal | normal */
// @import "../node_modules/@fontsource/fira-mono/500.css"; /* heavier normal? */
@import "../node_modules/@fontsource/fira-mono/700.css"; /* bold | normal */
/* Fontsource Fira Code */
@import "../node_modules/@fontsource/fira-code/300.css"; /* light | normal */
@import "../node_modules/@fontsource/fira-code/400.css"; /* normal | normal */
// @import "../node_modules/@fontsource/fira-code/500.css"; /* heavier normal? */
// @import "../node_modules/@fontsource/fira-code/600.css"; /* heavier normal? */
@import "../node_modules/@fontsource/fira-code/700.css"; /* bold | normal */

View file

@ -0,0 +1,56 @@
/* Hamburger settings overrides */
$hamburger-padding-x: 0.95rem !default;
$hamburger-padding-y: 0.625rem !default;
$hamburger-layer-width: 1.5rem !default;
$hamburger-layer-height: 2px !default;
$hamburger-layer-spacing: 6px !default;
// * skipping default color, using color from bootstrap instead, below
// $hamburger-layer-color: rgba(0, 0, 0, 0.55) !default;
$hamburger-layer-border-radius: 0.25rem !default;
$hamburger-hover-opacity: 0.7 !default;
// * skipping default color, using color from bootstrap instead, below
// $hamburger-active-layer-color: $hamburger-layer-color !default;
$hamburger-active-hover-opacity: $hamburger-hover-opacity !default;
/* import hamburgers sass */
@import "../node_modules/hamburgers/_sass/hamburgers/hamburgers.scss";
/* correct colors to match bootstrap defaults */
.navbar-light {
.hamburger {
&.navbar-toggler {
border: 1px $navbar-light-toggler-border-color solid !important;
.hamburger-inner {
&,
&::before,
&::after {
background-color: $navbar-light-color;
}
}
&.is-active {
.hamburger-inner::after {
background-color: $navbar-light-color;
}
}
}
}
}
.navbar-dark {
.hamburger {
&.navbar-toggler {
border: 1px $navbar-dark-toggler-border-color solid !important;
.hamburger-inner {
&,
&::before,
&::after {
background-color: $navbar-dark-color;
}
}
&.is-active {
.hamburger-inner::after {
background-color: $navbar-dark-color;
}
}
}
}
}

View file

@ -0,0 +1,43 @@
/* LiveView specific classes for your customizations */
/* hides the feedback field help if liveview indicates field not touched yet */
.phx-no-feedback.invalid-feedback,
.phx-no-feedback .invalid-feedback,
.phx-orphaned-feedback.phx-no-feedback .invalid-feedback {
display: none;
}
.phx-orphaned-feedback .invalid-feedback {
display: block !important;
}
/* sets default bootstrap form-control styles if field not touched yet */
.phx-no-feedback .form-control.is-valid,
.phx-no-feedback .form-control.is-invalid {
border: $input-border-width solid $input-border-color;
background-image: none;
&:focus {
color: $input-focus-color;
background-color: $input-focus-bg;
border-color: $input-focus-border-color;
outline: 0;
@if $enable-shadows {
@include box-shadow($input-box-shadow, $input-focus-box-shadow);
} @else {
// Avoid using mixin so we can pass custom focus shadow properly
box-shadow: $input-focus-box-shadow;
}
}
}
.phx-click-loading {
opacity: 0.5;
transition: opacity 1s ease-out;
}
.phx-disconnected {
cursor: wait;
}
.phx-disconnected * {
pointer-events: none;
}

View file

@ -0,0 +1,21 @@
/*
SVG ICON SYSTEM
per https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4
*/
.icon {
display: inline-flex;
align-self: center;
}
.icon svg,
.icon img {
height: 1em;
width: 1em;
fill: currentColor;
}
.icon.baseline svg,
.icon img {
top: 0.125em;
position: relative;
}

View file

@ -1,90 +1,63 @@
/* This file is for your main application css. */ /* Fonts */
@import "./phoenix.css"; @import "fonts";
/* LiveView specific classes for your customizations */ /* Load Bootstrap v5 and customizations */
.phx-no-feedback.invalid-feedback, @import "bs-load";
.phx-no-feedback .invalid-feedback {
display: none; /*SVG ICON SYSTEM*/
@import "svg-icons";
/* LiveView specific CSS */
@import "phx-liveview";
/* Navbar toggler icon override */
@import "nav-burger";
/* extra */
.was-validated .no-valid-style .form-check-input:valid,
.form-check-input.is-valid {
border-color: inherit !important;
}
.was-validated .no-valid-style .form-check-input:valid ~ .form-check-label,
.no-valid-style .form-check-input.is-valid ~ .form-check-label {
color: inherit !important;
}
.table-nonfluid {
width: auto !important;
}
.cursor-pointer {
cursor: pointer !important;
} }
.phx-click-loading { /*
opacity: 0.5; icons in input fields
transition: opacity 1s ease-out; */
/* enable absolute positioning */
.inner-addon {
position: relative;
} }
.phx-disconnected{ /* style icon */
cursor: wait; .inner-addon > .icon {
} position: absolute;
.phx-disconnected *{ padding: 0.5625rem 0.5rem;
pointer-events: none; pointer-events: none;
} }
.phx-modal { /* align icon */
opacity: 1!important; .inner-addon > .icon.is-left {
position: fixed; left: 0px;
z-index: 1; }
left: 0; .inner-addon > .icon.is-right {
top: 0; right: 0px;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
} }
.phx-modal-content { /* add padding */
background-color: #fefefe; .left-addon input,
margin: 15% auto; .left-addon select {
padding: 20px; padding-left: 2rem;
border: 1px solid #888;
width: 80%;
} }
.right-addon input,
.phx-modal-close { .right-addon select {
color: #aaa; padding-right: 2rem;
float: right;
font-size: 28px;
font-weight: bold;
}
.phx-modal-close:hover,
.phx-modal-close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
/* Alerts and form errors */
.alert {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
}
.alert-info {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
}
.alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
}
.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
.alert p {
margin-bottom: 0;
}
.alert:empty {
display: none;
}
.invalid-feedback {
color: #a94442;
display: block;
margin: -1rem 0 2rem;
} }

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,12 @@
const togglerBtn = document.getElementById("navbarSupportedContentToggler");
const navbarContent = document.getElementById("navbarSupportedContent");
navbarContent.addEventListener("show.bs.collapse", () => {
console.log("opening navbar content");
togglerBtn.classList.toggle("is-active");
});
navbarContent.addEventListener("hide.bs.collapse", () => {
console.log("closing navbar content");
togglerBtn.classList.toggle("is-active");
});

View file

@ -1,7 +1,12 @@
// We need to import the CSS so that webpack will load it. // We need to import the CSS so that webpack will load it.
// The MiniCssExtractPlugin is used to separate it out into // The MiniCssExtractPlugin is used to separate it out into
// its own CSS file. // its own CSS file.
import "../css/app.scss" import "../css/app.scss";
// Import icons for sprite-loader
// navbar brand icon
import "../node_modules/@mdi/svg/svg/desktop-classic.svg"; // brand
// other:///
// webpack automatically bundles all modules in your // webpack automatically bundles all modules in your
// entry points. Those entry points can be configured // entry points. Those entry points can be configured
@ -12,25 +17,34 @@ import "../css/app.scss"
// import {Socket} from "phoenix" // import {Socket} from "phoenix"
// import socket from "./socket" // import socket from "./socket"
// //
import "phoenix_html" import "phoenix_html";
import {Socket} from "phoenix" import { Socket } from "phoenix";
import topbar from "topbar" import topbar from "topbar";
import {LiveSocket} from "phoenix_live_view" import { LiveSocket } from "phoenix_live_view";
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") // // Bootstrap v5 js imports
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) import "bootstrap/js/dist/collapse";
// import "bootstrap/js/dist/dropdown";
// Bootstrap helpers
import "./_hamburger-helper";
let csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
});
// Show progress bar on live navigation and form submits // Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" });
window.addEventListener("phx:page-loading-start", info => topbar.show()) window.addEventListener("phx:page-loading-start", (info) => topbar.show());
window.addEventListener("phx:page-loading-stop", info => topbar.hide()) window.addEventListener("phx:page-loading-stop", (info) => topbar.hide());
// connect if there are any LiveViews on the page // connect if there are any LiveViews on the page
liveSocket.connect() liveSocket.connect();
// expose liveSocket on window for web console debug logs and latency simulation: // expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug() // >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim() // >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket window.liveSocket = liveSocket;

13736
assets/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,28 +3,43 @@
"description": " ", "description": " ",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"deploy": "webpack --mode production", "deploy": "NODE_ENV=production webpack --mode production",
"watch": "webpack --mode development --watch" "watch": "webpack --mode development --watch"
}, },
"dependencies": { "dependencies": {
"@fontsource/fira-code": "^4.x",
"@fontsource/fira-mono": "^4.x",
"@fontsource/lato": "^4.x",
"@fontsource/righteous": "^4.x",
"@mdi/svg": "^5.9.55",
"@popperjs/core": "^2.8.4",
"bootstrap": "^5.0.0-beta3",
"bootstrap-icons": "^1.4.0",
"hamburgers": "^1.1.3",
"phoenix": "file:../deps/phoenix", "phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html", "phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view", "phoenix_live_view": "file:../deps/phoenix_live_view",
"topbar": "^0.1.4" "topbar": "^0.1.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.0.0", "@babel/core": "^7.x",
"@babel/preset-env": "^7.0.0", "@babel/preset-env": "^7.x",
"babel-loader": "^8.0.0", "autoprefixer": "^10.2.4",
"copy-webpack-plugin": "^5.1.1", "babel-loader": "^8.x",
"css-loader": "^3.4.2", "copy-webpack-plugin": "^8.x",
"hard-source-webpack-plugin": "^0.13.1", "css-loader": "^5.x",
"mini-css-extract-plugin": "^0.9.0", "css-minimizer-webpack-plugin": "^1.x",
"node-sass": "^4.13.1", "file-loader": "^6.2.0",
"optimize-css-assets-webpack-plugin": "^5.0.1", "glob-all": "^3.2.1",
"sass-loader": "^8.0.2", "mini-css-extract-plugin": "^1.x",
"terser-webpack-plugin": "^2.3.2", "postcss": "^8.2.6",
"webpack": "^4.41.5", "postcss-loader": "^5.0.0",
"webpack-cli": "^3.3.2" "postcss-scss": "^3.0.4",
"purgecss-webpack-plugin": "^4.0.2",
"sass": "^1.x",
"sass-loader": "^11.x",
"svg-sprite-loader": "^6.x",
"webpack": "^5.x",
"webpack-cli": "^4.x"
} }
} }

4
assets/postcss.config.js Normal file
View file

@ -0,0 +1,4 @@
module.exports = {
parser: require("postcss-scss"),
plugins: [require("autoprefixer")],
};

View file

@ -1,53 +1,105 @@
const path = require('path'); const path = require("path");
const glob = require('glob'); const glob = require("glob-all");
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require('terser-webpack-plugin'); const CopyWebpackPlugin = require("copy-webpack-plugin");
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const SpriteLoaderPlugin = require("svg-sprite-loader/plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin'); const PurgecssPlugin = require("purgecss-webpack-plugin");
module.exports = (env, options) => { module.exports = (env, options) => {
const devMode = options.mode !== 'production'; const devMode = options.mode !== "production";
return { return {
optimization: {
minimizer: [
new TerserPlugin({ cache: true, parallel: true, sourceMap: devMode }),
new OptimizeCSSAssetsPlugin({})
]
},
entry: { entry: {
'app': glob.sync('./vendor/**/*.js').concat(['./js/app.js']) app: glob.sync("./vendor/**/*.js").concat(["./js/app.js"]),
}, },
output: { output: {
filename: '[name].js', path: path.resolve(__dirname, "../priv/static/js"),
path: path.resolve(__dirname, '../priv/static/js'), filename: "[name].js",
publicPath: '/js/' publicPath: "/js/",
}, },
devtool: devMode ? 'eval-cheap-module-source-map' : undefined,
module: { module: {
rules: [ rules: [
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: 'babel-loader' loader: "babel-loader",
} },
}, },
{ {
test: /\.[s]?css$/, test: /\.[s]?css$/,
use: [ use: [
MiniCssExtractPlugin.loader, MiniCssExtractPlugin.loader,
'css-loader', "css-loader",
'sass-loader', "sass-loader",
"postcss-loader",
], ],
},
{
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: "file-loader",
options: {
esModule: false,
name: "[name].[ext]",
outputPath: "../fonts",
},
},
],
},
{
test: /\.svg$/,
loader: "svg-sprite-loader",
options: {
extract: true,
spriteFilename: "icons.svg",
publicPath: "../images/",
symbolId: (filePath) => {
if (filePath.includes("bootstrap-icons")) {
return `bi-${path.basename(filePath).slice(0, -4)}`;
} else if (filePath.includes("@mdi")) {
return `mdi-${path.basename(filePath).slice(0, -4)}`;
} else if (filePath.includes("heroicons")) {
if (filePath.includes("outline")) {
return `hio-${path.basename(filePath).slice(0, -4)}`;
} else {
return `his-${path.basename(filePath).slice(0, -4)}`;
} }
] } else {
return `${path.basename(filePath).slice(0, -4)}`;
}
},
},
},
],
}, },
plugins: [ plugins: [
new MiniCssExtractPlugin({ filename: '../css/app.css' }), new MiniCssExtractPlugin({ filename: "../css/app.css" }),
new CopyWebpackPlugin([{ from: 'static/', to: '../' }]) new SpriteLoaderPlugin({ plainSprite: true }),
new CopyWebpackPlugin({
patterns: [{ from: "static/", to: "../" }],
}),
].concat(
devMode
? []
: [
new PurgecssPlugin({
paths: glob.sync([
"../**/*.html.leex",
"../**/*.html.eex",
"../**/views/**/*.ex",
"../**/live/**/*.ex",
"./js/**/*.js",
]),
safelist: [/phx/, /topbar/],
}),
] ]
.concat(devMode ? [new HardSourceWebpackPlugin()] : []) ),
} optimization: {
minimizer: ["...", new CssMinimizerPlugin()],
},
devtool: devMode ? "source-map" : undefined,
};
}; };

View file

@ -7,6 +7,11 @@
# General application configuration # General application configuration
use Mix.Config use Mix.Config
config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
# Custom application global variables
config :home73k, :app_global_vars, time_zone: "America/New_York"
# Configures the endpoint # Configures the endpoint
config :home73k, Home73kWeb.Endpoint, config :home73k, Home73kWeb.Endpoint,
url: [host: "localhost"], url: [host: "localhost"],

View file

@ -16,7 +16,8 @@ config :home73k, Home73kWeb.Endpoint,
"node_modules/webpack/bin/webpack.js", "node_modules/webpack/bin/webpack.js",
"--mode", "--mode",
"development", "development",
"--watch-stdin", "--watch",
"--watch-options-stdin",
cd: Path.expand("../assets", __DIR__) cd: Path.expand("../assets", __DIR__)
] ]
] ]

View file

@ -6,4 +6,9 @@ defmodule Home73k do
Contexts are also responsible for managing your data, regardless Contexts are also responsible for managing your data, regardless
if it comes from the database, an external API or others. if it comes from the database, an external API or others.
""" """
@app_vars Application.get_env(:bones73k, :app_global_vars, time_zone: "America/New_York")
@app_time_zone @app_vars[:time_zone]
def app_time_zone, do: @app_time_zone
end end

View file

@ -31,7 +31,8 @@ defmodule Home73kWeb do
quote do quote do
use Phoenix.View, use Phoenix.View,
root: "lib/home73k_web/templates", root: "lib/home73k_web/templates",
namespace: Home73kWeb namespace: Home73kWeb,
pattern: "**/*"
# Import convenience functions from controllers # Import convenience functions from controllers
import Phoenix.Controller, import Phoenix.Controller,
@ -48,6 +49,7 @@ defmodule Home73kWeb do
layout: {Home73kWeb.LayoutView, "live.html"} layout: {Home73kWeb.LayoutView, "live.html"}
unquote(view_helpers()) unquote(view_helpers())
import Home73kWeb.LiveHelpers
end end
end end
@ -56,6 +58,7 @@ defmodule Home73kWeb do
use Phoenix.LiveComponent use Phoenix.LiveComponent
unquote(view_helpers()) unquote(view_helpers())
import Home73kWeb.LiveHelpers
end end
end end
@ -87,6 +90,9 @@ defmodule Home73kWeb do
# Import basic rendering functionality (render, render_layout, etc) # Import basic rendering functionality (render, render_layout, etc)
import Phoenix.View import Phoenix.View
# Import SVG Icon helper
import Home73kWeb.IconHelpers
import Home73kWeb.ErrorHelpers import Home73kWeb.ErrorHelpers
import Home73kWeb.Gettext import Home73kWeb.Gettext
alias Home73kWeb.Router.Helpers, as: Routes alias Home73kWeb.Router.Helpers, as: Routes

View file

@ -23,7 +23,7 @@ defmodule Home73kWeb.Endpoint do
plug Plug.Static, plug Plug.Static,
at: "/", at: "/",
from: :home73k, from: :home73k,
gzip: false, gzip: true,
only: ~w(css fonts images js favicon.ico robots.txt) only: ~w(css fonts images js favicon.ico robots.txt)
# Code reloading can be explicitly enabled under the # Code reloading can be explicitly enabled under the

View file

@ -0,0 +1,13 @@
defmodule Home73kWeb.LiveHelpers do
@doc """
Performs the {:noreply, socket} for a given socket.
This helps make the noreply pipeable
"""
def live_noreply(socket), do: {:noreply, socket}
@doc """
Performs the {:ok, socket} for a given socket.
This helps make the ok reply pipeable
"""
def live_okreply(socket), do: {:ok, socket}
end

View file

@ -3,7 +3,7 @@ defmodule Home73kWeb.PageLive do
@impl true @impl true
def mount(_params, _session, socket) do def mount(_params, _session, socket) do
{:ok, assign(socket, query: "", results: %{})} {:ok, assign(socket, query: "", results: %{}, page_title: "~")}
end end
@impl true @impl true

View file

@ -0,0 +1,61 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
<div class="container">
<h1 class="fs-4 my-0 py-0 lh-base">
<%= link to: Routes.page_path(@conn, :index), class: "navbar-brand fs-4" do %>
<%= icon_div @conn, "mdi-desktop-classic", [class: "icon baseline me-1 fs-3"] %>
<span class="fw-light" style="font-family: Righteous;">\\AdamPion73k</span>
<% end %>
</h1>
<button class="hamburger hamburger--squeeze collapsed navbar-toggler" id="navbarSupportedContentToggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="hamburger-box d-flex">
<span class="hamburger-inner"></span>
</span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<%# nav LEFT items %>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<%# ACTIVE page link example %>
<%# <li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li> %>
<%# DISABLED page link example %>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
<%# <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownExample" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdownExample">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li> %>
</ul>
<%# nav RIGHT items %>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Resume</a>
</li>
<%= link nav_link_opts(@conn, to: Routes.page_path(@conn, :index), class: "btn btn-outline-dark d-none d-lg-block") do %>
<%#= icon_div @conn, "bi-door-open", [class: "icon baseline"] %>
Log in
<% end %>
</ul>
</div>
</div>
</nav>

View file

@ -1,5 +1,19 @@
<main role="main" class="container"> <main role="main" class="container">
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> <%# phoenix flash alerts: %>
<div class="row justify-content-center">
<div class="col-md-12 col-lg-10 col-xxl-8 ">
<%= for {kind, class} <- alert_kinds() do %>
<%= if flash_content = get_flash(@conn, kind) do %>
<div class="alert <%= class %> alert-dismissible fade show" role="alert">
<%= flash_content %>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<% end %>
<% end %>
</div>
</div>
<%= @inner_content %> <%= @inner_content %>
</main> </main>

View file

@ -1,11 +1,19 @@
<main role="main" class="container"> <main role="main" class="container">
<p class="alert alert-info" role="alert"
phx-click="lv:clear-flash"
phx-value-key="info"><%= live_flash(@flash, :info) %></p>
<p class="alert alert-danger" role="alert" <%# liveview flash alerts: %>
phx-click="lv:clear-flash" <div class="row justify-content-center">
phx-value-key="error"><%= live_flash(@flash, :error) %></p> <div class="col-md-12 col-lg-10 col-xxl-8 ">
<%= for {kind, class} <- alert_kinds() do %>
<%= if flash_content = live_flash(@flash, kind) do %>
<div class="alert <%= class %> alert-dismissible fade show" role="alert" id="lv-alert-<%= kind %>" phx-hook="AlertRemover" data-key="<%= kind %>">
<%= flash_content %>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<% end %>
<% end %>
</div>
</div>
<%= @inner_content %> <%= @inner_content %>
</main> </main>

View file

@ -0,0 +1,29 @@
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" id="navbarDropdownUserMenu" data-bs-toggle="dropdown" aria-expanded="false">
<%= icon_div @conn, "bi-person-circle", [class: "icon baseline me-1"] %>
Hello
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownUserMenu">
<li><hr class="dropdown-divider"></li>
<li>
<%= link nav_link_opts(@conn, to: Routes.page_path(@conn, :index), class: "dropdown-item") do %>
<%= icon_div @conn, "bi-people", [class: "icon baseline me-1"] %>
Users
<% end %>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<%= link nav_link_opts(@conn, to: Routes.page_path(@conn, :index), class: "dropdown-item") do %>
<%= icon_div @conn, "bi-sliders", [class: "icon baseline me-1"] %>
Settings
<% end %>
</li>
</ul>
</li>

View file

@ -5,26 +5,19 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"/> <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<%= csrf_meta_tag() %> <%= csrf_meta_tag() %>
<%= live_title_tag assigns[:page_title] || "Home73k", suffix: " · Phoenix Framework" %> <%= live_title_tag assigns[:page_title] || "", prefix: assigns[:page_title] && "73k.us \\ " || "73k.us" %>
<meta name="author" content="Adam Piontek"/>
<link rel="me" href="mailto:adam@73k.us"/>
<link rel="me" href="sms:+16462341697"/>
<link rel="authorization_endpoint" href="https://indieauth.com/auth"/>
<link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/> <link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script> <script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</head> </head>
<body> <body>
<header>
<section class="container"> <%= render "_navbar.html", assigns %>
<nav role="navigation">
<ul>
<li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
<%= if function_exported?(Routes, :live_dashboard_path, 2) do %>
<li><%= link "LiveDashboard", to: Routes.live_dashboard_path(@conn, :home) %></li>
<% end %>
</ul>
</nav>
<a href="https://phoenixframework.org/" class="phx-logo">
<img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
</a>
</section>
</header>
<%= @inner_content %> <%= @inner_content %>
</body> </body>
</html> </html>

View file

@ -0,0 +1,38 @@
defmodule Home73kWeb.IconHelpers do
@moduledoc """
Generate SVG sprite use tags for SVG icons
"""
use Phoenix.HTML
alias Home73kWeb.Router.Helpers, as: Routes
def icon_div(conn, name, div_opts \\ [], svg_opts \\ []) do
content_tag(:div, tag_opts(name, div_opts)) do
icon_svg(conn, name, svg_opts)
end
end
def icon_svg(conn, name, opts \\ []) do
opts = aria_hidden?(opts)
content_tag(:svg, tag_opts(name, opts)) do
~E"""
<%= if title = Keyword.get(opts, :aria_label), do: content_tag(:title, title) %>
<%= tag(:use, "xlink:href": Routes.static_path(conn, "/images/icons.svg##{name}")) %>
"""
end
end
defp tag_opts(name, opts) do
Keyword.update(opts, :class, name, fn c -> "#{c} #{name}" end)
end
defp aria_hidden?(opts) do
case Keyword.get(opts, :aria_hidden) do
"false" -> Keyword.drop(opts, [:aria_hidden])
false -> Keyword.drop(opts, [:aria_hidden])
"true" -> opts
_ -> Keyword.put(opts, :aria_hidden, "true")
end
end
end

View file

@ -1,3 +1,21 @@
defmodule Home73kWeb.LayoutView do defmodule Home73kWeb.LayoutView do
use Home73kWeb, :view use Home73kWeb, :view
def nav_link_opts(conn, opts) do
case Keyword.get(opts, :to) == Phoenix.Controller.current_path(conn) do
false -> opts
true -> Keyword.update(opts, :class, "active", fn c -> "#{c} active" end)
end
end
def alert_kinds do
[
success: "alert-success",
info: "alert-info",
error: "alert-danger",
warning: "alert-warning",
primary: "alert-primary",
secondary: "alert-secondary"
]
end
end end

View file

@ -43,7 +43,13 @@ defmodule Home73k.MixProject do
{:telemetry_poller, "~> 0.4"}, {:telemetry_poller, "~> 0.4"},
{:gettext, "~> 0.11"}, {:gettext, "~> 0.11"},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"} {:plug_cowboy, "~> 2.0"},
{:tzdata, "~> 1.1"},
# Additional packages
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
{:sobelow, "~> 0.8", only: :dev}
] ]
end end

View file

@ -1,13 +1,21 @@
%{ %{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
"cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
"cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
"credo": {:hex, :credo, "1.5.5", "e8f422026f553bc3bebb81c8e8bf1932f498ca03339856c7fec63d3faac8424b", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dd8623ab7091956a855dc9f3062486add9c52d310dfd62748779c4315d8247de"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.30.0", "22ebbe681a5d3777cdd830ca091b1b806d33c3449c26312eadca7f7be685c0c8", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "a9e128a4ca9bb71f11affa315b6768a9ad326d5996ff1e92acf1d7a01a10076a"}, "floki": {:hex, :floki, "0.30.0", "22ebbe681a5d3777cdd830ca091b1b806d33c3449c26312eadca7f7be685c0c8", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "a9e128a4ca9bb71f11affa315b6768a9ad326d5996ff1e92acf1d7a01a10076a"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"}, "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.5.8", "71cfa7a9bb9a37af4df98939790642f210e35f696b935ca6d9d9c55a884621a4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "35ded0a32f4836168c7ab6c33b88822eccd201bcd9492125a9bea4c54332d955"}, "phoenix": {:hex, :phoenix, "1.5.8", "71cfa7a9bb9a37af4df98939790642f210e35f696b935ca6d9d9c55a884621a4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "35ded0a32f4836168c7ab6c33b88822eccd201bcd9492125a9bea4c54332d955"},
"phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"}, "phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.4.0", "87990e68b60213d7487e65814046f9a2bed4a67886c943270125913499b3e5c3", [:mix], [{:ecto_psql_extras, "~> 0.4.1 or ~> 0.5", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0 or ~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "8d52149e58188e9e4497cc0d8900ab94d9b66f96998ec38c47c7a4f8f4f50e57"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.4.0", "87990e68b60213d7487e65814046f9a2bed4a67886c943270125913499b3e5c3", [:mix], [{:ecto_psql_extras, "~> 0.4.1 or ~> 0.5", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0 or ~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "8d52149e58188e9e4497cc0d8900ab94d9b66f96998ec38c47c7a4f8f4f50e57"},
@ -18,7 +26,11 @@
"plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"}, "plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.0", "da9d49ee7e6bb1c259d36ce6539cd45ae14d81247a2b0c90edf55e2b50507f7b", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5cfe67ad464b243835512aa44321cee91faed6ea868d7fb761d7016e02915c3d"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.0", "da9d49ee7e6bb1c259d36ce6539cd45ae14d81247a2b0c90edf55e2b50507f7b", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5cfe67ad464b243835512aa44321cee91faed6ea868d7fb761d7016e02915c3d"},
"telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"}, "telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
"tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
} }