diff --git a/.bundle/config b/.bundle/config deleted file mode 100644 index f154eb2..0000000 --- a/.bundle/config +++ /dev/null @@ -1,2 +0,0 @@ ---- -BUNDLE_FORCE_RUBY_PLATFORM: "true" diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..e945e12 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,5 @@ +[ + import_deps: [:phoenix], + plugins: [Phoenix.LiveView.HTMLFormatter], + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] +] diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..2eb706d --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -e + +STAGED=$( + git --no-pager diff --name-only --no-color --cached --diff-filter=d | + # Remove quotations used to surrounding filenames with special characters. + sed -e "s/^\"//" -e "s/\"$//g" +) + +MIX_TARGETS=() +WEB_TARGETS=() +while IFS= read -r FILENAME +do + if [[ "$FILENAME" =~ .*\.exs?$ ]]; then + MIX_TARGETS+=("${FILENAME}") + elif [[ "$FILENAME" =~ .*\.jsx?$ ]] || [[ "$FILENAME" =~ .*\.tsx?$ ]]; then + WEB_TARGETS+=("${FILENAME}") + fi +done <<< "$STAGED" + +if (( ${#MIX_TARGETS[@]} )); then + mix format "${MIX_TARGETS[@]}" + git add "${MIX_TARGETS[@]}" +fi + +if (( ${#WEB_TARGETS[@]} )); then + prettier --write "${WEB_TARGETS[@]}" + git add "${WEB_TARGETS[@]}" +fi diff --git a/.gitignore b/.gitignore index e99b3ee..c5bcf77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,42 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Temporary files, for example, from tests. +/tmp/ + +# Ignore package tarball (built via "mix hex.build"). +portfolio-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + # Directory used by `direnv` to hold `use flake`-generated profiles. /.direnv/ # A symlink produced by default when running `nix build`. /result - -# The jekyll-produced static bundle. -_site/ diff --git a/404.html b/404.html deleted file mode 100644 index 086a5c9..0000000 --- a/404.html +++ /dev/null @@ -1,25 +0,0 @@ ---- -permalink: /404.html -layout: default ---- - - - -
-

404

- -

Page not found :(

-

The requested page could not be found.

-
diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 4ea0343..0000000 --- a/Gemfile +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gem "jekyll", "~> 4.3.2" -gem "lagrange", "~> 4.0" - -# If you have any plugins, put them here! -group :jekyll_plugins do - gem "jekyll-feed", "~> 0.12" - gem "jekyll-seo-tag", "~> 2.8" - gem "jekyll-sitemap", "~> 1.4" - gem "jekyll-paginate-v2", "~> 3.0" -end diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index f3cc2bb..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,92 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) - colorator (1.1.0) - concurrent-ruby (1.2.2) - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - eventmachine (1.2.7) - ffi (1.16.3) - forwardable-extended (2.6.0) - google-protobuf (3.25.1) - http_parser.rb (0.8.0) - i18n (1.14.1) - concurrent-ruby (~> 1.0) - jekyll (4.3.2) - addressable (~> 2.4) - colorator (~> 1.0) - em-websocket (~> 0.5) - i18n (~> 1.0) - jekyll-sass-converter (>= 2.0, < 4.0) - jekyll-watch (~> 2.0) - kramdown (~> 2.3, >= 2.3.1) - kramdown-parser-gfm (~> 1.0) - liquid (~> 4.0) - mercenary (>= 0.3.6, < 0.5) - pathutil (~> 0.9) - rouge (>= 3.0, < 5.0) - safe_yaml (~> 1.0) - terminal-table (>= 1.8, < 4.0) - webrick (~> 1.7) - jekyll-feed (0.17.0) - jekyll (>= 3.7, < 5.0) - jekyll-paginate (1.1.0) - jekyll-paginate-v2 (3.0.0) - jekyll (>= 3.0, < 5.0) - jekyll-sass-converter (3.0.0) - sass-embedded (~> 1.54) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-sitemap (1.4.0) - jekyll (>= 3.7, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - lagrange (4.0.0) - jekyll (~> 4.2) - jekyll-feed (~> 0.6) - jekyll-paginate (~> 1.1) - jekyll-seo-tag (~> 2.6) - jekyll-sitemap (~> 1.3) - liquid (4.0.4) - listen (3.8.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - mercenary (0.4.0) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (5.0.4) - rake (13.1.0) - rb-fsevent (0.11.2) - rb-inotify (0.10.1) - ffi (~> 1.0) - rexml (3.2.6) - rouge (4.2.0) - safe_yaml (1.0.5) - sass-embedded (1.69.5) - google-protobuf (~> 3.23) - rake (>= 13.0.0) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - unicode-display_width (2.5.0) - webrick (1.8.1) - -PLATFORMS - ruby - -DEPENDENCIES - jekyll (~> 4.3.2) - jekyll-feed (~> 0.12) - jekyll-paginate-v2 (~> 3.0) - jekyll-seo-tag (~> 2.8) - jekyll-sitemap (~> 1.4) - lagrange (~> 4.0) - -BUNDLED WITH - 2.4.22 diff --git a/README.md b/README.md index b5c5792..ca21f30 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,95 @@ -# Jekyll Flake Template +# Portfolio -This is a template for bootstrapping a [Jekyll](https://jekyllrb.com/)-based -project (version 4.3.2) with the [lagrange](https://github.com/LeNPaul/Lagrange) -theme (version 4.0). [direnv](https://direnv.net/) can be used to launch a dev -shell upon entering this directory (refer to `.envrc`). Otherwise run via: +This is my main personal website. It uses Elixir (version 1.15.7, Erlang/OTP +25) and the [Phoenix](https://www.phoenixframework.org/) (version 1.7.10) +framework. [direnv](https://direnv.net/) can be used to launch a dev shell upon +entering this directory (refer to `.envrc`). Otherwise run via: ```bash $ nix develop ``` -Start the server by running: -``` -$ jekyll serve [--watch] + +## Quickstart + +Run the Phoenix setup command and then start the local server: +```bash +$ mix deps.get +$ mix assets.setup +$ mix phx.server ``` -## Building +## Blog -Dependencies are managed using [bundix](https://github.com/nix-community/bundix). -If you make any changes to the `Gemfile`, run the following: +Blog posts exist under the nested `blog_html/` directory. Markdown files are +converted to HTML using pandoc (version 3.1.9). For example, ```bash -$ bundix -l +$ cd lib/portfolio_web/controllers/blog_html +$ pandoc \ +> --to=html \ +> --toc \ +> --template=template.html \ +> --output tagless_final_parsing.html.heex \ +> tagless_final_parsing.md ``` -This will update the `Gemfile.lock` and `gemset.nix` files. Afterward you can -run: + +Styling is maintained within the `blog.css` file. This file is generated by +copying the CSS made within a standalone invocation of `pandoc`. For example, ```bash -$ nix build +$ pandoc --standalone --output output.html --highlight-style=zenburn ``` -Note that we need the `.bundle/config` file to workaround issues bundix has with -pre-built, platform-specific gems. Refer to -[PR #68](https://github.com/nix-community/bundix/pull/68) for more details. + +## Dependencies + +### Backend + +Mix dependencies are packaged using [mix2nix](https://github.com/ydlr/mix2nix). +After updating your `mix.lock` file, make sure to re-run the following: +```bash +$ mix2nix > deps.nix +``` +As of now, `mix2nix` cannot handle git dependencies found inside the `mix.lock` +file. If you have git dependencies, add them manually or use +[FODs](https://nixos.org/manual/nixpkgs/stable/#packaging-beam-applications). + +### Frontend + +Frontend dependencies (i.e. assets found in the `/assets` folder) are packaged +using [node2nix](https://github.com/svanderburg/node2nix). You can generate the +relevant nix files for import using the following sequence of commands: +```bash +$ cd assets +$ rm -r node_modules # If this directory exists. +$ node2nix -l +``` +In the above, we must remove `node_modules` (if it exists). Otherwise the +node packages will be included in the Nix build, influencing the outcome of +`node2nix`. The above generates three files: + +* `node-packages.nix` + * Captures the packages that can be deployed (including all its required + dependencies) +* `node-env.nix` + * Contains build logic +* `default.nix` + * A composition expression allowing users to deploy the package. For an + example of this deployment, refer to `flake.nix` + +NOTE: Do not update the lock version used in `assets`. `node2nix` currently only +supports lock versions 1 and 2. + +## Language Server + +The [elixir-ls](https://github.com/elixir-lsp/elixir-ls) LSP (version 0.17.10) +and [typescript-language-server](https://github.com/typescript-language-server/typescript-language-server) +(version 4.1.2) is included in this flake. + +## Formatting + +Formatting depends on [prettier](https://prettier.io/) (version 3.1.0) and the +`mix format` task. A `pre-commit` hook is included in `.githooks` that can be +used to format all `*.exs?`, `*.jsx?`, and `*.tsx?` files prior to commit. +Install via: +```bash +$ git config --local core.hooksPath .githooks/ +``` +If running [direnv](https://direnv.net/), this hook is installed automatically +when entering the directory. diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 526807f..0000000 --- a/_config.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Welcome to Jekyll! -# -# This config file is meant for settings that affect your whole blog, values -# which you are expected to set up once and rarely edit after that. If you find -# yourself editing this file very often, consider using Jekyll's data files -# feature for the data you need to update frequently. -# -# For technical reasons, this file is *NOT* reloaded automatically when you use -# 'bundle exec jekyll serve'. If you change this file, please restart the server process. -# -# If you need help with YAML syntax, here are some quick references for you: -# https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml -# https://learnxinyminutes.com/docs/yaml/ -# -# Site settings -# These are used to personalize your new site. If you look in the HTML files, -# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. -# You can create any custom variable you would like, and they will be accessible -# in the templates via {{ site.myvariable }}. - -title: Joshua Potter -description: My portfolio -author: Joshua Potter -github_username: jrpotter - -theme: lagrange -plugins: - - jekyll-feed - - jekyll-paginate-v2 - - jekyll-seo-tag - - jekyll-sitemap - -pagination: - enabled: true - per_page: 8 - permalink: '/page/:num/' - limit: 0 - sort_field: 'date' - sort_reverse: true - -markdown: kramdown -highlighter: route -permalink: /:title - -hide_post_share: true -hide_related_posts: true - -# Exclude from processing. -# The following items will not be processed, by default. -# Any item listed under the `exclude:` key here will be automatically added to -# the internal "default list". -# -# Excluded items can be processed by explicitly listing the directories or -# their entries' file path in the `include:` list. -# -# exclude: -# - .sass-cache/ -# - .jekyll-cache/ -# - gemfiles/ -# - Gemfile -# - Gemfile.lock -# - node_modules/ -# - vendor/bundle/ -# - vendor/cache/ -# - vendor/gems/ -# - vendor/ruby/ diff --git a/_data/settings.yml b/_data/settings.yml deleted file mode 100644 index 9fb906e..0000000 --- a/_data/settings.yml +++ /dev/null @@ -1,18 +0,0 @@ -menu: - - name: "Home" - - name: "Projects" - url: "projects.html" - - name: "Archive" - url: "archive.html" - -social: - - icon: "github" - link: "https://github.com/jrpotter" - - icon: "rss-square" - link: "https://blog.jrpotter.com" - - icon: "book" - link: "https://bookshelf.jrpotter.com" - - icon: "linkedin" - link: "https://www.linkedin.com/in/jrpotter2112/" - - icon: "hdd-o" - link: "https://www.zotero.org/jrpotter2112/library" diff --git a/_includes/footer.html b/_includes/footer.html deleted file mode 100644 index d3252b9..0000000 --- a/_includes/footer.html +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/_includes/head.html b/_includes/head.html deleted file mode 100644 index db3e1e5..0000000 --- a/_includes/head.html +++ /dev/null @@ -1,29 +0,0 @@ - - Portfolio • Joshua Potter - - - - - - {% feed_meta %} - - - - - - - - {% if jekyll.environment == "production" %} - - {% endif %} - - {% seo %} - diff --git a/_includes/post-paginator.html b/_includes/post-paginator.html deleted file mode 100644 index c8a6f6e..0000000 --- a/_includes/post-paginator.html +++ /dev/null @@ -1,32 +0,0 @@ -{% if paginator.total_pages > 1 %} - - -{% endif %} diff --git a/_layouts/archive.html b/_layouts/archive.html deleted file mode 100644 index db739f3..0000000 --- a/_layouts/archive.html +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: default ---- - - diff --git a/_layouts/home.html b/_layouts/home.html deleted file mode 100644 index 58270d8..0000000 --- a/_layouts/home.html +++ /dev/null @@ -1,45 +0,0 @@ ---- -layout: default ---- - -{% if paginator.page_path contains "index" %} -

- Most applications listed below are served from NixOS - machines hosted on Digital Ocean. - Configuration files for each of my machines can be found here. - If interested in starting a similar hosting solution, consider getting a $200 - credit using my referral link. -

-{% endif %} - -{% for post in paginator.posts %} -
-

- {{ post.title }} -

- {% if post.image %} -
- - - -
- {% endif %} -

-

- {% for tag in post.tags %} -
{{ tag }}
- {% endfor %} -
-

{{ post.content }}

- - {% if post.href %} - {{ post.href }} - - {% endif %} - - {{ post.date | date_to_string }} - -

-
-{% endfor %} - -{% include post-paginator.html %} diff --git a/_layouts/post.html b/_layouts/post.html deleted file mode 100644 index 3cf8329..0000000 --- a/_layouts/post.html +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: default ---- - -

- {% if page.href %} - {{ page.title }} - {% else %} - {{ page.title }} - {% endif %} -

-{% if page.image %} -
- - - -
-{% endif %} -
- {{ content }} -
-{% if site.hide_post_date != true %} - {% include post-date.html %} -{% endif %} -{% if site.hide_post_share != true %} - {% include social-sharing.html %} -{% endif %} -{% if site.hide_related_posts != true %} - {% include related-posts.html %} -{% endif %} -{% if site.data.settings.disqus.comments %} - {% include disqus.html %} -{% endif %} diff --git a/_posts/2015-03-24-mini-java.md b/_posts/2015-03-24-mini-java.md deleted file mode 100644 index a5ca4ee..0000000 --- a/_posts/2015-03-24-mini-java.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -title: Mini Java -categories: project -tags: [compiler, java] -href: "https://git.jrpotter.com/r/mini-java" -image: java.png ---- - -A Java implemention of a subset of Java. Generates code that targets mJAM, an -abstract machine included in the source that supports running `miniJava`. In -particular, this implementation supports various primitives, array types, and, -to a certain degree, classes. diff --git a/_posts/2015-06-20-fifth.md b/_posts/2015-06-20-fifth.md deleted file mode 100644 index 0992916..0000000 --- a/_posts/2015-06-20-fifth.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -title: Fifth -categories: project -tags: [cellular-automata, python] -href: "https://git.jrpotter.com/r/fifth" -image: conway-gol.jpg ---- - -A library for parsing various rulesets for cellular automata machines (CAMs). -The parsed CAM is displayed using [matplotlib](https://matplotlib.org). For -instance, this library parses ruleset `B3/S23` and then produces a running -visualization of Conway's Game of Life. diff --git a/_posts/2015-10-01-pong.md b/_posts/2015-10-01-pong.md deleted file mode 100644 index 914272d..0000000 --- a/_posts/2015-10-01-pong.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: post -title: Pong -categories: project -tags: [fpga, mips-assembly, verilog] -href: "https://git.jrpotter.com/r/pong" -image: pong.jpg ---- - -An implementation of the classic pong video game, written from scratch on an -Artix FPGA using System Verilog. This works on a custom ALU intended to process -an arbitrary MIPS program with modified memory configuration: `.text 0x0000` -and `.data 0x2000`. A memory mapped IO scheme is used to draw to the monitor and -interact with the keyboard. diff --git a/_posts/2017-07-25-vim-highlight.md b/_posts/2017-07-25-vim-highlight.md deleted file mode 100644 index 5259246..0000000 --- a/_posts/2017-07-25-vim-highlight.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -title: Highlight Plugin -categories: project -tags: [vim] -href: "https://git.jrpotter.com/r/vim-highlight" -image: vim.png ---- - -A small Vim plugin that maintains a custom registry for manipulating highlights. -This registry allows highlighting different keywords without overriding previous -searches. Includes a small snippet for including the active highlight from -within the statusline. diff --git a/_posts/2017-07-25-vim-join.md b/_posts/2017-07-25-vim-join.md deleted file mode 100644 index 6523ea9..0000000 --- a/_posts/2017-07-25-vim-join.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: post -title: Join Plugin -categories: project -tags: [vim] -href: "https://git.jrpotter.com/r/vim-join" -image: vim.png ---- - -A small Vim plugin that joins a number of lines together and then breaks them -again with respect to the `textwidth` parameter. This enables re-flowing a set -of lines similar in manner to `fold` or `fmt`. diff --git a/_posts/2018-09-01-bill-gates-honey.md b/_posts/2018-09-01-bill-gates-honey.md deleted file mode 100644 index be29ec6..0000000 --- a/_posts/2018-09-01-bill-gates-honey.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: post -title: Bill Gates and Honey -categories: other -tags: [podcast] -href: "https://open.spotify.com/episode/2CaNBdpgZofhXDGCs2QsPW" -image: would-you-blabber.png ---- - -In our first episode of Would You Blabber, we discuss honey, truck drivers, and -hurting those more fortunate than us. diff --git a/_posts/2018-09-02-bubbles-funny-bones.md b/_posts/2018-09-02-bubbles-funny-bones.md deleted file mode 100644 index a1b3bd6..0000000 --- a/_posts/2018-09-02-bubbles-funny-bones.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: post -title: Bubbles and Funny Bones -categories: other -tags: [podcast] -href: "https://open.spotify.com/episode/0MeLBj9jw3lPXBxRKevODo" -image: would-you-blabber.png ---- - -In this episode of Would You Blabber, we discuss bubble boys and precognition. diff --git a/_posts/2019-12-01-bananas-buildings-batman.md b/_posts/2019-12-01-bananas-buildings-batman.md deleted file mode 100644 index d1d7049..0000000 --- a/_posts/2019-12-01-bananas-buildings-batman.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: post -title: Bananas, Buildings, and Batman -categories: other -tags: [podcast] -href: "https://open.spotify.com/episode/3XrpanmTQiZhXjYqLP3dBd" -image: would-you-blabber.png ---- - -Bananas, buildings, and Batman! diff --git a/_posts/2020-01-01-huh-huh-huh.md b/_posts/2020-01-01-huh-huh-huh.md deleted file mode 100644 index afc9a58..0000000 --- a/_posts/2020-01-01-huh-huh-huh.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: post -title: Huh, Huh, and Huh ft. Kenny Yi -categories: other -tags: [podcast] -href: "https://open.spotify.com/episode/44oNp0ctwyk590298xWHwA" -image: would-you-blabber.png ---- - -In this episode of Would You Blabber, we explore the world of glogging, huh-ing, -and old soft-serve ice cream. diff --git a/_posts/2020-03-29-postlude.md b/_posts/2020-03-29-postlude.md deleted file mode 100644 index db7d70b..0000000 --- a/_posts/2020-03-29-postlude.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -title: Postlude -categories: project -tags: [haskell] -href: "https://git.jrpotter.com/r/postlude" -image: haskell.png ---- - -An example of a custom-rolled [Prelude](https://hackage.haskell.org/package/base-4.19.0.0/docs/Prelude.html). -Serves as a fairly comprehensive list of imports I found relevant across the -various Haskell projects I worked on as well as a demonstration on how -forwarding imports with Haskell works. diff --git a/_posts/2021-04-01-looped.md b/_posts/2021-04-01-looped.md deleted file mode 100644 index 3a199b1..0000000 --- a/_posts/2021-04-01-looped.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: post -title: Looped -categories: home -tags: [python, swift, kotlin, vue] -image: looped.png ---- - -VP of engineering at Looped, the "Ultimate Virtual Venue". Featured on -[Forbes](https://www.forbes.com/sites/ericfuller/2021/01/06/loopedthe-app-helping-fans-mingle-and-meet-artists-personally-during-live-streamed-events) -and [TechCrunch](https://techcrunch.com/2021/03/02/looped-raises-7-7m-to-expand-its-interactive-live-event-platform). -Led development on the Kotlin-based [Android app](https://play.google.com/store/apps/details?id=com.vipvr.android) -(50K+ downloads, 4.5 star review), the Swift-based iOS app (100K+ downloads, -4.8 star review), the Vue-based web app, and the Django-based backend. diff --git a/_posts/2021-12-08-blog.md b/_posts/2021-12-08-blog.md deleted file mode 100644 index 3ff7413..0000000 --- a/_posts/2021-12-08-blog.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -title: Blog -categories: home -tags: [jekyll, nix, ruby] -href: "https://blog.jrpotter.com" -image: jekyll.png ---- - -I occasionally write about (usually) technical concepts in my blog. Originally -powered by Github Pages, I've since moved the [Jekyll](https://jekyllrb.com/)-based -project to a self-hosting solution. Theming is provided by -[Chirpy](https://github.com/cotes2020/jekyll-theme-chirpy). diff --git a/_posts/2021-12-28-homesync.md b/_posts/2021-12-28-homesync.md deleted file mode 100644 index 04df6f8..0000000 --- a/_posts/2021-12-28-homesync.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: post -title: Homesync -categories: project -tags: [git, nix, rust] -href: "https://git.jrpotter.com/r/homesync" -image: git-branches.png ---- - -An experimental Rust-based project for automatically syncing files across your -desktop to a git repository. Allows upstream and downstream syncing with a -single command, without any need to copy files manually to and from a git -repository. Separately, a daemon can be spawned that watches files for changes -and pushes/pulls them as they happen. diff --git a/_posts/2022-07-02-anki-synonyms.md b/_posts/2022-07-02-anki-synonyms.md deleted file mode 100644 index 82dabd7..0000000 --- a/_posts/2022-07-02-anki-synonyms.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: post -title: Anki Synonyms -categories: project -tags: [anki, python] -href: "https://git.jrpotter.com/r/anki-synonyms" -image: anki.png ---- - -An [Anki](https://apps.ankiweb.net/) plugin for specifying synonyms within -flashcard question and answer prompts. diff --git a/_posts/2023-02-05-bookshelf.md b/_posts/2023-02-05-bookshelf.md deleted file mode 100644 index 2e8cb6b..0000000 --- a/_posts/2023-02-05-bookshelf.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -title: Bookshelf -categories: home -tags: [latex, lean, nix] -href: "https://bookshelf.jrpotter.com" -image: lean.svg ---- - -A collection of books I am actively studying. Usually mathematics or -computer-science based, I aim to prove concepts as I encounter them using the -[Lean](https://lean-lang.org/) interactive theorem prover. All proofs are also -available in [LaTeX](https://www.latex-project.org/). diff --git a/_posts/2023-04-09-were-the-crew.md b/_posts/2023-04-09-were-the-crew.md deleted file mode 100644 index c01919e..0000000 --- a/_posts/2023-04-09-were-the-crew.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -title: We're the Crew -categories: other -tags: [music] -href: "https://gusvieweg.bandcamp.com/track/were-the-crew" -image: smash-rap.jpg ---- - -A Smash Bros. Ultimate rap made in collaboration with my friend -[Gus](https://www.gusvieweg.com/) and fiancée Brittany. - - diff --git a/_posts/2023-11-17-bootstrap.md b/_posts/2023-11-17-bootstrap.md deleted file mode 100644 index 80c8900..0000000 --- a/_posts/2023-11-17-bootstrap.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: post -title: Bootstrap -categories: project -tags: [c, nix] -href: "https://git.jrpotter.com/r/bootstrap" -image: shoelaces.jpg ---- - -A C-based CLI for initializing projects in a flexible but deterministic way. -Originally motivated to serve as a better alternative to [Nix flake templates](https://github.com/NixOS/templates), -`bootstrap` allows you to provide different parameters to custom initialization -scripts akin to `npm init`, `django-admin startproject`, etc. - diff --git a/_posts/2023-11-26-boardwise.md b/_posts/2023-11-26-boardwise.md deleted file mode 100644 index ff88095..0000000 --- a/_posts/2023-11-26-boardwise.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: post -title: BoardWise -categories: home -tags: [elixir, react, nix] -href: "https://boardwise.gg" -image: boardwise.svg ---- - -A [Phoenix](https://www.phoenixframework.org/)- and -[React](https://react.dev/)-based project that provides an interface for finding -chess coaches. This serves as an alternative to those found on -[Lichess](https://lichess.org/coach) and [Chess.com](https://www.chess.com/coaches). -Based on the [Tailwind Studio](https://tailwindui.com/templates/studio) theme. diff --git a/_posts/2023-12-10-nixos-configuration.md b/_posts/2023-12-10-nixos-configuration.md deleted file mode 100644 index 5fc23b2..0000000 --- a/_posts/2023-12-10-nixos-configuration.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: post -title: NixOS Configuration -categories: project -tags: [lua, nix] -href: "https://git.jrpotter.com/r/nixos-configuration" -image: nix.png ---- - -The [nix](https://nixos.org) configuration files used to declaratively describe -my local and remote machines. The site you are on now is declared within this -project! diff --git a/_posts/2023-12-14-bookshelf-doc.md b/_posts/2023-12-14-bookshelf-doc.md deleted file mode 100644 index 72037b2..0000000 --- a/_posts/2023-12-14-bookshelf-doc.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -title: Bookshelf Doc Generator -categories: project -tags: [latex, lean] -href: "https://git.jrpotter.com/r/bookshelf-doc" -image: lean.svg ---- - -A fork of [doc-gen4](https://github.com/leanprover/doc-gen4) tightly coupled to -my [bookshelf](https://git.jrpotter.com/r/bookshelf) project. This augments the -`:docs` facet to convert LaTeX files into PDFs and then list them in the -generated navbar. diff --git a/_posts/2023-12-23-forgejo.md b/_posts/2023-12-23-forgejo.md deleted file mode 100644 index 92e0e31..0000000 --- a/_posts/2023-12-23-forgejo.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: post -title: Forgejo -categories: home -tags: [git, nix] -href: "https://git.jrpotter.com" -image: forgejo.svg ---- - -A self-hosted [forgejo](https://forgejo.org/) instance. For the most part, my -[GitHub](https://github.com) repositories are a mirror of those found here. -There do exist a few repos though that live exclusively on either site. diff --git a/_posts/2023-12-30-joker-card.md b/_posts/2023-12-30-joker-card.md deleted file mode 100644 index 851df88..0000000 --- a/_posts/2023-12-30-joker-card.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: post -title: Sketchbook Shuffle -categories: other -tags: [art] -href: "https://www.twobeeindustries.com/playing-card-instructions" -image: joker-card.png ---- - -My contribution to a collective art project in which different members of the -community choose a playing card to design. I designed the red joker. Once all -cards are submitted, a deck will be printed for purchase. diff --git a/_posts/2024-01-30-notebook.md b/_posts/2024-01-30-notebook.md deleted file mode 100644 index b23a0b0..0000000 --- a/_posts/2024-01-30-notebook.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -title: Notebook -categories: home -tags: [nix, quartz] -href: "https://notebook.jrpotter.com" -image: quartz.png ---- - -A static site generated with [Quartz](https://quartz.jzhao.xyz/). Contains a -collection of my transcribed notes, primarily Markdown managed using -[Obsidian](https://obsidian.md/). Hidden are a collection of -[Anki](https://apps.ankiweb.net/) flashcards. diff --git a/_posts/2024-04-12-hide-and-seek.md b/_posts/2024-04-12-hide-and-seek.md deleted file mode 100644 index 6323436..0000000 --- a/_posts/2024-04-12-hide-and-seek.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: post -title: Hide and Seek -categories: home -tags: [elixir, phoenix, react] -href: "https://hideandseek.live" -image: hide-and-seek.png ---- - -Realtime hide-and-seek application for the town of Fort Collins, built using -[Phoenix](https://www.phoenixframework.org/) and [React](https://react.dev/). -Group up with friends, designate a hider, and allow the rest of the group to -find the hider in a city-wide search. Use various clues to narrow down the -search space at the cost of potentially giving the hider means of thwarting the -search. diff --git a/_sass/_-sections-dir.scss b/_sass/_-sections-dir.scss deleted file mode 100644 index 03311d6..0000000 --- a/_sass/_-sections-dir.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "base", - "code", - "default", - "home", - "post", - "social-icons" diff --git a/_sass/_home.scss b/_sass/_home.scss deleted file mode 100644 index 6ed3d78..0000000 --- a/_sass/_home.scss +++ /dev/null @@ -1,33 +0,0 @@ -.home-description, .posts-container { - padding-bottom: 25px; -} - -.thumbnail-container { - text-align: center; - - img { - max-width: 100%; - max-height: 220px; - } -} - -ul.pagination { - list-style: none; - display: flex; - gap: 2rem; - padding: 0; - margin-bottom: 0; - justify-content: center; - - li.disabled { - opacity: 0.5; - - .page-link i { - cursor: not-allowed; - } - } - - .page-link i { - font-size: 1.75rem; - } -} diff --git a/_sass/_post.scss b/_sass/_post.scss deleted file mode 100644 index 3f4fb43..0000000 --- a/_sass/_post.scss +++ /dev/null @@ -1,48 +0,0 @@ -.page-image-container { - text-align: center; - - img { - width: auto; - height: 200px; - } -} - -.post-tags { - display: flex; - gap: 8px; - - div { - background: #e0e0e0; - border-radius: 999px; - padding: 2px 8px; - font-size: 16px; - } -} - -.post-date { - display: block; - margin-top: 3px; - margin-bottom: 1rem; - color: $light-gray-color; - font-family: $sans-serif-font-family; - font-size: 0.8rem; -} - -@media (max-width: $elements-responsive-width) { - .posts h1 { - font-size: 1.5rem; - } -} - -.related { - padding-bottom: 2rem; -} - -.related-posts { - padding-left: 0px; - list-style: none; -} - -.related-posts a { - text-decoration: none; -} diff --git a/_sass/social-icons.scss b/_sass/social-icons.scss deleted file mode 100644 index a248d91..0000000 --- a/_sass/social-icons.scss +++ /dev/null @@ -1,70 +0,0 @@ -/* - Social media icons -*/ - -.social-icons a, .sharing-icons a { - padding-right: 10px; -} - -@mixin social-media-icon($color, $transition){ - -webkit-transition: $transition; - -o-transition: $transition; - -ms-transition: $transition; - -moz-transition: $transition; - transition: $transition; - &:hover{ - color: $color; - } -} - -.sharing-icons { - .fa-envelope { - padding: 5px; - @include social-media-icon($envelope-color, $icon-transition-time); - } - - .fa-twitter { - padding: 5px; - @include social-media-icon($twitter-color, $icon-transition-time); - } - - .fa-instagram { - padding: 5px; - @include social-media-icon($instagram-color, $icon-transition-time); - } - - .fa-github { - padding: 5px; - @include social-media-icon($github-color, $icon-transition-time); - } - - .fa-linkedin { - padding: 5px; - @include social-media-icon($linkedin-color, $icon-transition-time); - } - - .fa-facebook { - padding: 5px; - @include social-media-icon($facebook-color, $icon-transition-time); - } - - .fa-pinterest { - padding: 5px; - @include social-media-icon($pinterest-color, $icon-transition-time); - } - - .fa-medium { - padding: 5px; - @include social-media-icon($medium-color, $icon-transition-time); - } - - .fa-codepen { - padding: 5px; - @include social-media-icon($codepen-color, $icon-transition-time); - } - - .fa-rss-square { - padding: 5px; - @include social-media-icon($rss-color, $icon-transition-time); - } -} diff --git a/assets/css/app.css b/assets/css/app.css new file mode 100644 index 0000000..3211549 --- /dev/null +++ b/assets/css/app.css @@ -0,0 +1,131 @@ +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + +/* Links */ + +a { + display: inline-block; + text-decoration: underline; + width: fit-content; +} + +/* Table of contents */ + +#TOC li { + padding-top: 0.5rem; +} + +#TOC .h3 { + margin-left: 1rem; +} + +#TOC .h4 { + margin-left: 2rem; +} + +/* Headers */ + +h1, h2, h3, h4 { + padding: 0.5rem 0; + width: fit-content; +} + +h1 { + border-bottom: 1px dashed white; + font-size: 1.25rem; + line-height: 1.75rem; +} + +h2, h3, h4 { + border-bottom: 1px dotted white; +} + +h2 { + font-size: 1.125rem; + line-height: 1.75rem; +} + +h4 { + font-size: 0.875rem; + line-height: 1.25rem; +} + +/* Figures */ + +figure { + text-align: center; +} + +figure img { + margin: 0 auto; + max-width: 768px; +} + +/* Footnote */ + +#footnotes { + margin-top: 2rem; +} + +#footnotes ol { + padding-top: 1rem; + padding-left: 1rem; + list-style-type: decimal; +} + +/* Syntax highlighting */ + +code { + font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace; + font-size: 85%; + margin: 0; +} + +pre { + border-left: 1px solid gray; + padding-left: 1rem; + margin: 1em 0; + overflow: auto; +} + +pre code { + padding: 0; + overflow: visible; + overflow-wrap: normal; +} + +.sourceCode { + background-color: transparent; + overflow: visible; +} + +code span.al { color: #ffcfaf; } /* Alert */ +code span.an { color: #7f9f7f; font-weight: bold; } /* Annotation */ +code span.at { } /* Attribute */ +code span.bn { color: #dca3a3; } /* BaseN */ +code span.bu { } /* BuiltIn */ +code span.cf { color: #f0dfaf; } /* ControlFlow */ +code span.ch { color: #dca3a3; } /* Char */ +code span.cn { color: #dca3a3; font-weight: bold; } /* Constant */ +code span.co { color: #7f9f7f; } /* Comment */ +code span.cv { color: #7f9f7f; font-weight: bold; } /* CommentVar */ +code span.do { color: #7f9f7f; } /* Documentation */ +code span.dt { color: #dfdfbf; } /* DataType */ +code span.dv { color: #dcdccc; } /* DecVal */ +code span.er { color: #c3bf9f; } /* Error */ +code span.ex { } /* Extension */ +code span.fl { color: #c0bed1; } /* Float */ +code span.fu { color: #efef8f; } /* Function */ +code span.im { } /* Import */ +code span.in { color: #7f9f7f; font-weight: bold; } /* Information */ +code span.kw { color: #f0dfaf; } /* Keyword */ +code span.op { color: #f0efd0; } /* Operator */ +code span.ot { color: #efef8f; } /* Other */ +code span.pp { color: #ffcfaf; font-weight: bold; } /* Preprocessor */ +code span.sc { color: #dca3a3; } /* SpecialChar */ +code span.ss { color: #cc9393; } /* SpecialString */ +code span.st { color: #cc9393; } /* String */ +code span.va { } /* Variable */ +code span.vs { color: #cc9393; } /* VerbatimString */ +code span.wa { color: #7f9f7f; font-weight: bold; } /* Warning */ diff --git a/assets/default.nix b/assets/default.nix new file mode 100644 index 0000000..b795f70 --- /dev/null +++ b/assets/default.nix @@ -0,0 +1,17 @@ +# This file has been generated by node2nix 1.11.1. Do not edit! + +{pkgs ? import { + inherit system; + }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs_14"}: + +let + nodeEnv = import ./node-env.nix { + inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript; + inherit pkgs nodejs; + libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null; + }; +in +import ./node-packages.nix { + inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit; + inherit nodeEnv; +} diff --git a/assets/img/anki.png b/assets/img/anki.png deleted file mode 100644 index 3d49902..0000000 Binary files a/assets/img/anki.png and /dev/null differ diff --git a/assets/img/boardwise.svg b/assets/img/boardwise.svg deleted file mode 100644 index e2fa1bc..0000000 --- a/assets/img/boardwise.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/img/conway-gol.jpg b/assets/img/conway-gol.jpg deleted file mode 100644 index 494aedb..0000000 Binary files a/assets/img/conway-gol.jpg and /dev/null differ diff --git a/assets/img/forgejo.svg b/assets/img/forgejo.svg deleted file mode 100644 index 6b46a4a..0000000 --- a/assets/img/forgejo.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Forgejo logo - Caesar Schinas - - - - - - - - - - - - - diff --git a/assets/img/git-branches.png b/assets/img/git-branches.png deleted file mode 100644 index eac5e02..0000000 Binary files a/assets/img/git-branches.png and /dev/null differ diff --git a/assets/img/haskell.png b/assets/img/haskell.png deleted file mode 100644 index 3dcf926..0000000 Binary files a/assets/img/haskell.png and /dev/null differ diff --git a/assets/img/hide-and-seek.png b/assets/img/hide-and-seek.png deleted file mode 100644 index 3a3498d..0000000 Binary files a/assets/img/hide-and-seek.png and /dev/null differ diff --git a/assets/img/java.png b/assets/img/java.png deleted file mode 100644 index 6b9f1d0..0000000 Binary files a/assets/img/java.png and /dev/null differ diff --git a/assets/img/jekyll.png b/assets/img/jekyll.png deleted file mode 100644 index 57af994..0000000 Binary files a/assets/img/jekyll.png and /dev/null differ diff --git a/assets/img/joker-card.png b/assets/img/joker-card.png deleted file mode 100644 index a3eb604..0000000 Binary files a/assets/img/joker-card.png and /dev/null differ diff --git a/assets/img/lean.svg b/assets/img/lean.svg deleted file mode 100644 index 92e242c..0000000 --- a/assets/img/lean.svg +++ /dev/null @@ -1,314 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/img/looped.png b/assets/img/looped.png deleted file mode 100644 index 3a476b4..0000000 Binary files a/assets/img/looped.png and /dev/null differ diff --git a/assets/img/nix.png b/assets/img/nix.png deleted file mode 100644 index 309c8bb..0000000 Binary files a/assets/img/nix.png and /dev/null differ diff --git a/assets/img/pong.jpg b/assets/img/pong.jpg deleted file mode 100644 index 01cc15b..0000000 Binary files a/assets/img/pong.jpg and /dev/null differ diff --git a/assets/img/quartz.png b/assets/img/quartz.png deleted file mode 100644 index fdef4f5..0000000 Binary files a/assets/img/quartz.png and /dev/null differ diff --git a/assets/img/shoelaces.jpg b/assets/img/shoelaces.jpg deleted file mode 100644 index dfdd3c9..0000000 Binary files a/assets/img/shoelaces.jpg and /dev/null differ diff --git a/assets/img/smash-rap.jpg b/assets/img/smash-rap.jpg deleted file mode 100644 index 678f308..0000000 Binary files a/assets/img/smash-rap.jpg and /dev/null differ diff --git a/assets/img/vim.png b/assets/img/vim.png deleted file mode 100644 index 5dec3e6..0000000 Binary files a/assets/img/vim.png and /dev/null differ diff --git a/assets/img/would-you-blabber.png b/assets/img/would-you-blabber.png deleted file mode 100644 index ce73d27..0000000 Binary files a/assets/img/would-you-blabber.png and /dev/null differ diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..60fabb4 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,44 @@ +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import { Socket } from "phoenix" +import { LiveSocket } from "phoenix_live_view" +import topbar from "../vendor/topbar" + +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 +topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" }) +window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300)) +window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket diff --git a/assets/node-env.nix b/assets/node-env.nix new file mode 100644 index 0000000..bc1e366 --- /dev/null +++ b/assets/node-env.nix @@ -0,0 +1,689 @@ +# This file originates from node2nix + +{lib, stdenv, nodejs, python2, pkgs, libtool, runCommand, writeTextFile, writeShellScript}: + +let + # Workaround to cope with utillinux in Nixpkgs 20.09 and util-linux in Nixpkgs master + utillinux = if pkgs ? utillinux then pkgs.utillinux else pkgs.util-linux; + + python = if nodejs ? python then nodejs.python else python2; + + # Create a tar wrapper that filters all the 'Ignoring unknown extended header keyword' noise + tarWrapper = runCommand "tarWrapper" {} '' + mkdir -p $out/bin + + cat > $out/bin/tar <> $out/nix-support/hydra-build-products + ''; + }; + + # Common shell logic + installPackage = writeShellScript "install-package" '' + installPackage() { + local packageName=$1 src=$2 + + local strippedName + + local DIR=$PWD + cd $TMPDIR + + unpackFile $src + + # Make the base dir in which the target dependency resides first + mkdir -p "$(dirname "$DIR/$packageName")" + + if [ -f "$src" ] + then + # Figure out what directory has been unpacked + packageDir="$(find . -maxdepth 1 -type d | tail -1)" + + # Restore write permissions to make building work + find "$packageDir" -type d -exec chmod u+x {} \; + chmod -R u+w "$packageDir" + + # Move the extracted tarball into the output folder + mv "$packageDir" "$DIR/$packageName" + elif [ -d "$src" ] + then + # Get a stripped name (without hash) of the source directory. + # On old nixpkgs it's already set internally. + if [ -z "$strippedName" ] + then + strippedName="$(stripHash $src)" + fi + + # Restore write permissions to make building work + chmod -R u+w "$strippedName" + + # Move the extracted directory into the output folder + mv "$strippedName" "$DIR/$packageName" + fi + + # Change to the package directory to install dependencies + cd "$DIR/$packageName" + } + ''; + + # Bundle the dependencies of the package + # + # Only include dependencies if they don't exist. They may also be bundled in the package. + includeDependencies = {dependencies}: + lib.optionalString (dependencies != []) ( + '' + mkdir -p node_modules + cd node_modules + '' + + (lib.concatMapStrings (dependency: + '' + if [ ! -e "${dependency.packageName}" ]; then + ${composePackage dependency} + fi + '' + ) dependencies) + + '' + cd .. + '' + ); + + # Recursively composes the dependencies of a package + composePackage = { name, packageName, src, dependencies ? [], ... }@args: + builtins.addErrorContext "while evaluating node package '${packageName}'" '' + installPackage "${packageName}" "${src}" + ${includeDependencies { inherit dependencies; }} + cd .. + ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} + ''; + + pinpointDependencies = {dependencies, production}: + let + pinpointDependenciesFromPackageJSON = writeTextFile { + name = "pinpointDependencies.js"; + text = '' + var fs = require('fs'); + var path = require('path'); + + function resolveDependencyVersion(location, name) { + if(location == process.env['NIX_STORE']) { + return null; + } else { + var dependencyPackageJSON = path.join(location, "node_modules", name, "package.json"); + + if(fs.existsSync(dependencyPackageJSON)) { + var dependencyPackageObj = JSON.parse(fs.readFileSync(dependencyPackageJSON)); + + if(dependencyPackageObj.name == name) { + return dependencyPackageObj.version; + } + } else { + return resolveDependencyVersion(path.resolve(location, ".."), name); + } + } + } + + function replaceDependencies(dependencies) { + if(typeof dependencies == "object" && dependencies !== null) { + for(var dependency in dependencies) { + var resolvedVersion = resolveDependencyVersion(process.cwd(), dependency); + + if(resolvedVersion === null) { + process.stderr.write("WARNING: cannot pinpoint dependency: "+dependency+", context: "+process.cwd()+"\n"); + } else { + dependencies[dependency] = resolvedVersion; + } + } + } + } + + /* Read the package.json configuration */ + var packageObj = JSON.parse(fs.readFileSync('./package.json')); + + /* Pinpoint all dependencies */ + replaceDependencies(packageObj.dependencies); + if(process.argv[2] == "development") { + replaceDependencies(packageObj.devDependencies); + } + else { + packageObj.devDependencies = {}; + } + replaceDependencies(packageObj.optionalDependencies); + replaceDependencies(packageObj.peerDependencies); + + /* Write the fixed package.json file */ + fs.writeFileSync("package.json", JSON.stringify(packageObj, null, 2)); + ''; + }; + in + '' + node ${pinpointDependenciesFromPackageJSON} ${if production then "production" else "development"} + + ${lib.optionalString (dependencies != []) + '' + if [ -d node_modules ] + then + cd node_modules + ${lib.concatMapStrings (dependency: pinpointDependenciesOfPackage dependency) dependencies} + cd .. + fi + ''} + ''; + + # Recursively traverses all dependencies of a package and pinpoints all + # dependencies in the package.json file to the versions that are actually + # being used. + + pinpointDependenciesOfPackage = { packageName, dependencies ? [], production ? true, ... }@args: + '' + if [ -d "${packageName}" ] + then + cd "${packageName}" + ${pinpointDependencies { inherit dependencies production; }} + cd .. + ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} + fi + ''; + + # Extract the Node.js source code which is used to compile packages with + # native bindings + nodeSources = runCommand "node-sources" {} '' + tar --no-same-owner --no-same-permissions -xf ${nodejs.src} + mv node-* $out + ''; + + # Script that adds _integrity fields to all package.json files to prevent NPM from consulting the cache (that is empty) + addIntegrityFieldsScript = writeTextFile { + name = "addintegrityfields.js"; + text = '' + var fs = require('fs'); + var path = require('path'); + + function augmentDependencies(baseDir, dependencies) { + for(var dependencyName in dependencies) { + var dependency = dependencies[dependencyName]; + + // Open package.json and augment metadata fields + var packageJSONDir = path.join(baseDir, "node_modules", dependencyName); + var packageJSONPath = path.join(packageJSONDir, "package.json"); + + if(fs.existsSync(packageJSONPath)) { // Only augment packages that exist. Sometimes we may have production installs in which development dependencies can be ignored + console.log("Adding metadata fields to: "+packageJSONPath); + var packageObj = JSON.parse(fs.readFileSync(packageJSONPath)); + + if(dependency.integrity) { + packageObj["_integrity"] = dependency.integrity; + } else { + packageObj["_integrity"] = "sha1-000000000000000000000000000="; // When no _integrity string has been provided (e.g. by Git dependencies), add a dummy one. It does not seem to harm and it bypasses downloads. + } + + if(dependency.resolved) { + packageObj["_resolved"] = dependency.resolved; // Adopt the resolved property if one has been provided + } else { + packageObj["_resolved"] = dependency.version; // Set the resolved version to the version identifier. This prevents NPM from cloning Git repositories. + } + + if(dependency.from !== undefined) { // Adopt from property if one has been provided + packageObj["_from"] = dependency.from; + } + + fs.writeFileSync(packageJSONPath, JSON.stringify(packageObj, null, 2)); + } + + // Augment transitive dependencies + if(dependency.dependencies !== undefined) { + augmentDependencies(packageJSONDir, dependency.dependencies); + } + } + } + + if(fs.existsSync("./package-lock.json")) { + var packageLock = JSON.parse(fs.readFileSync("./package-lock.json")); + + if(![1, 2].includes(packageLock.lockfileVersion)) { + process.stderr.write("Sorry, I only understand lock file versions 1 and 2!\n"); + process.exit(1); + } + + if(packageLock.dependencies !== undefined) { + augmentDependencies(".", packageLock.dependencies); + } + } + ''; + }; + + # Reconstructs a package-lock file from the node_modules/ folder structure and package.json files with dummy sha1 hashes + reconstructPackageLock = writeTextFile { + name = "reconstructpackagelock.js"; + text = '' + var fs = require('fs'); + var path = require('path'); + + var packageObj = JSON.parse(fs.readFileSync("package.json")); + + var lockObj = { + name: packageObj.name, + version: packageObj.version, + lockfileVersion: 2, + requires: true, + packages: { + "": { + name: packageObj.name, + version: packageObj.version, + license: packageObj.license, + bin: packageObj.bin, + dependencies: packageObj.dependencies, + engines: packageObj.engines, + optionalDependencies: packageObj.optionalDependencies + } + }, + dependencies: {} + }; + + function augmentPackageJSON(filePath, packages, dependencies) { + var packageJSON = path.join(filePath, "package.json"); + if(fs.existsSync(packageJSON)) { + var packageObj = JSON.parse(fs.readFileSync(packageJSON)); + packages[filePath] = { + version: packageObj.version, + integrity: "sha1-000000000000000000000000000=", + dependencies: packageObj.dependencies, + engines: packageObj.engines, + optionalDependencies: packageObj.optionalDependencies + }; + dependencies[packageObj.name] = { + version: packageObj.version, + integrity: "sha1-000000000000000000000000000=", + dependencies: {} + }; + processDependencies(path.join(filePath, "node_modules"), packages, dependencies[packageObj.name].dependencies); + } + } + + function processDependencies(dir, packages, dependencies) { + if(fs.existsSync(dir)) { + var files = fs.readdirSync(dir); + + files.forEach(function(entry) { + var filePath = path.join(dir, entry); + var stats = fs.statSync(filePath); + + if(stats.isDirectory()) { + if(entry.substr(0, 1) == "@") { + // When we encounter a namespace folder, augment all packages belonging to the scope + var pkgFiles = fs.readdirSync(filePath); + + pkgFiles.forEach(function(entry) { + if(stats.isDirectory()) { + var pkgFilePath = path.join(filePath, entry); + augmentPackageJSON(pkgFilePath, packages, dependencies); + } + }); + } else { + augmentPackageJSON(filePath, packages, dependencies); + } + } + }); + } + } + + processDependencies("node_modules", lockObj.packages, lockObj.dependencies); + + fs.writeFileSync("package-lock.json", JSON.stringify(lockObj, null, 2)); + ''; + }; + + # Script that links bins defined in package.json to the node_modules bin directory + # NPM does not do this for top-level packages itself anymore as of v7 + linkBinsScript = writeTextFile { + name = "linkbins.js"; + text = '' + var fs = require('fs'); + var path = require('path'); + + var packageObj = JSON.parse(fs.readFileSync("package.json")); + + var nodeModules = Array(packageObj.name.split("/").length).fill("..").join(path.sep); + + if(packageObj.bin !== undefined) { + fs.mkdirSync(path.join(nodeModules, ".bin")) + + if(typeof packageObj.bin == "object") { + Object.keys(packageObj.bin).forEach(function(exe) { + if(fs.existsSync(packageObj.bin[exe])) { + console.log("linking bin '" + exe + "'"); + fs.symlinkSync( + path.join("..", packageObj.name, packageObj.bin[exe]), + path.join(nodeModules, ".bin", exe) + ); + } + else { + console.log("skipping non-existent bin '" + exe + "'"); + } + }) + } + else { + if(fs.existsSync(packageObj.bin)) { + console.log("linking bin '" + packageObj.bin + "'"); + fs.symlinkSync( + path.join("..", packageObj.name, packageObj.bin), + path.join(nodeModules, ".bin", packageObj.name.split("/").pop()) + ); + } + else { + console.log("skipping non-existent bin '" + packageObj.bin + "'"); + } + } + } + else if(packageObj.directories !== undefined && packageObj.directories.bin !== undefined) { + fs.mkdirSync(path.join(nodeModules, ".bin")) + + fs.readdirSync(packageObj.directories.bin).forEach(function(exe) { + if(fs.existsSync(path.join(packageObj.directories.bin, exe))) { + console.log("linking bin '" + exe + "'"); + fs.symlinkSync( + path.join("..", packageObj.name, packageObj.directories.bin, exe), + path.join(nodeModules, ".bin", exe) + ); + } + else { + console.log("skipping non-existent bin '" + exe + "'"); + } + }) + } + ''; + }; + + prepareAndInvokeNPM = {packageName, bypassCache, reconstructLock, npmFlags, production}: + let + forceOfflineFlag = if bypassCache then "--offline" else "--registry http://www.example.com"; + in + '' + # Pinpoint the versions of all dependencies to the ones that are actually being used + echo "pinpointing versions of dependencies..." + source $pinpointDependenciesScriptPath + + # Patch the shebangs of the bundled modules to prevent them from + # calling executables outside the Nix store as much as possible + patchShebangs . + + # Deploy the Node.js package by running npm install. Since the + # dependencies have been provided already by ourselves, it should not + # attempt to install them again, which is good, because we want to make + # it Nix's responsibility. If it needs to install any dependencies + # anyway (e.g. because the dependency parameters are + # incomplete/incorrect), it fails. + # + # The other responsibilities of NPM are kept -- version checks, build + # steps, postprocessing etc. + + export HOME=$TMPDIR + cd "${packageName}" + runHook preRebuild + + ${lib.optionalString bypassCache '' + ${lib.optionalString reconstructLock '' + if [ -f package-lock.json ] + then + echo "WARNING: Reconstruct lock option enabled, but a lock file already exists!" + echo "This will most likely result in version mismatches! We will remove the lock file and regenerate it!" + rm package-lock.json + else + echo "No package-lock.json file found, reconstructing..." + fi + + node ${reconstructPackageLock} + ''} + + node ${addIntegrityFieldsScript} + ''} + + npm ${forceOfflineFlag} --nodedir=${nodeSources} ${npmFlags} ${lib.optionalString production "--production"} rebuild + + runHook postRebuild + + if [ "''${dontNpmInstall-}" != "1" ] + then + # NPM tries to download packages even when they already exist if npm-shrinkwrap is used. + rm -f npm-shrinkwrap.json + + npm ${forceOfflineFlag} --nodedir=${nodeSources} --no-bin-links --ignore-scripts ${npmFlags} ${lib.optionalString production "--production"} install + fi + + # Link executables defined in package.json + node ${linkBinsScript} + ''; + + # Builds and composes an NPM package including all its dependencies + buildNodePackage = + { name + , packageName + , version ? null + , dependencies ? [] + , buildInputs ? [] + , production ? true + , npmFlags ? "" + , dontNpmInstall ? false + , bypassCache ? false + , reconstructLock ? false + , preRebuild ? "" + , dontStrip ? true + , unpackPhase ? "true" + , buildPhase ? "true" + , meta ? {} + , ... }@args: + + let + extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "preRebuild" "unpackPhase" "buildPhase" "meta" ]; + in + stdenv.mkDerivation ({ + name = "${name}${if version == null then "" else "-${version}"}"; + buildInputs = [ tarWrapper python nodejs ] + ++ lib.optional (stdenv.isLinux) utillinux + ++ lib.optional (stdenv.isDarwin) libtool + ++ buildInputs; + + inherit nodejs; + + inherit dontStrip; # Stripping may fail a build for some package deployments + inherit dontNpmInstall preRebuild unpackPhase buildPhase; + + compositionScript = composePackage args; + pinpointDependenciesScript = pinpointDependenciesOfPackage args; + + passAsFile = [ "compositionScript" "pinpointDependenciesScript" ]; + + installPhase = '' + source ${installPackage} + + # Create and enter a root node_modules/ folder + mkdir -p $out/lib/node_modules + cd $out/lib/node_modules + + # Compose the package and all its dependencies + source $compositionScriptPath + + ${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }} + + # Create symlink to the deployed executable folder, if applicable + if [ -d "$out/lib/node_modules/.bin" ] + then + ln -s $out/lib/node_modules/.bin $out/bin + + # Fixup all executables + ls $out/bin/* | while read i + do + file="$(readlink -f "$i")" + chmod u+rwx "$file" + if isScript "$file" + then + sed -i 's/\r$//' "$file" # convert crlf to lf + fi + done + fi + + # Create symlinks to the deployed manual page folders, if applicable + if [ -d "$out/lib/node_modules/${packageName}/man" ] + then + mkdir -p $out/share + for dir in "$out/lib/node_modules/${packageName}/man/"* + do + mkdir -p $out/share/man/$(basename "$dir") + for page in "$dir"/* + do + ln -s $page $out/share/man/$(basename "$dir") + done + done + fi + + # Run post install hook, if provided + runHook postInstall + ''; + + meta = { + # default to Node.js' platforms + platforms = nodejs.meta.platforms; + } // meta; + } // extraArgs); + + # Builds a node environment (a node_modules folder and a set of binaries) + buildNodeDependencies = + { name + , packageName + , version ? null + , src + , dependencies ? [] + , buildInputs ? [] + , production ? true + , npmFlags ? "" + , dontNpmInstall ? false + , bypassCache ? false + , reconstructLock ? false + , dontStrip ? true + , unpackPhase ? "true" + , buildPhase ? "true" + , ... }@args: + + let + extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" ]; + in + stdenv.mkDerivation ({ + name = "node-dependencies-${name}${if version == null then "" else "-${version}"}"; + + buildInputs = [ tarWrapper python nodejs ] + ++ lib.optional (stdenv.isLinux) utillinux + ++ lib.optional (stdenv.isDarwin) libtool + ++ buildInputs; + + inherit dontStrip; # Stripping may fail a build for some package deployments + inherit dontNpmInstall unpackPhase buildPhase; + + includeScript = includeDependencies { inherit dependencies; }; + pinpointDependenciesScript = pinpointDependenciesOfPackage args; + + passAsFile = [ "includeScript" "pinpointDependenciesScript" ]; + + installPhase = '' + source ${installPackage} + + mkdir -p $out/${packageName} + cd $out/${packageName} + + source $includeScriptPath + + # Create fake package.json to make the npm commands work properly + cp ${src}/package.json . + chmod 644 package.json + ${lib.optionalString bypassCache '' + if [ -f ${src}/package-lock.json ] + then + cp ${src}/package-lock.json . + chmod 644 package-lock.json + fi + ''} + + # Go to the parent folder to make sure that all packages are pinpointed + cd .. + ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} + + ${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }} + + # Expose the executables that were installed + cd .. + ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."} + + mv ${packageName} lib + ln -s $out/lib/node_modules/.bin $out/bin + ''; + } // extraArgs); + + # Builds a development shell + buildNodeShell = + { name + , packageName + , version ? null + , src + , dependencies ? [] + , buildInputs ? [] + , production ? true + , npmFlags ? "" + , dontNpmInstall ? false + , bypassCache ? false + , reconstructLock ? false + , dontStrip ? true + , unpackPhase ? "true" + , buildPhase ? "true" + , ... }@args: + + let + nodeDependencies = buildNodeDependencies args; + extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "unpackPhase" "buildPhase" ]; + in + stdenv.mkDerivation ({ + name = "node-shell-${name}${if version == null then "" else "-${version}"}"; + + buildInputs = [ python nodejs ] ++ lib.optional (stdenv.isLinux) utillinux ++ buildInputs; + buildCommand = '' + mkdir -p $out/bin + cat > $out/bin/shell < + // + plugin(({ addVariant }) => + addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"]) + ), + plugin(({ addVariant }) => + addVariant("phx-click-loading", [ + ".phx-click-loading&", + ".phx-click-loading &", + ]) + ), + plugin(({ addVariant }) => + addVariant("phx-submit-loading", [ + ".phx-submit-loading&", + ".phx-submit-loading &", + ]) + ), + plugin(({ addVariant }) => + addVariant("phx-change-loading", [ + ".phx-change-loading&", + ".phx-change-loading &", + ]) + ), + + // Embeds Heroicons (https://heroicons.com) into your app.css bundle + // See your `CoreComponents.icon/1` for more information. + // + plugin(function ({ matchComponents, theme }) { + let iconsDir = path.join(__dirname, "./vendor/heroicons/optimized") + let values = {} + let icons = [ + ["", "/24/outline"], + ["-solid", "/24/solid"], + ["-mini", "/20/solid"], + ] + icons.forEach(([suffix, dir]) => { + fs.readdirSync(path.join(iconsDir, dir)).forEach((file) => { + let name = path.basename(file, ".svg") + suffix + values[name] = { name, fullPath: path.join(iconsDir, dir, file) } + }) + }) + matchComponents( + { + hero: ({ name, fullPath }) => { + let content = fs + .readFileSync(fullPath) + .toString() + .replace(/\r?\n|\r/g, "") + return { + [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, + "-webkit-mask": `var(--hero-${name})`, + mask: `var(--hero-${name})`, + "mask-repeat": "no-repeat", + "background-color": "currentColor", + "vertical-align": "middle", + display: "inline-block", + width: theme("spacing.5"), + height: theme("spacing.5"), + } + }, + }, + { values } + ) + }), + ], +} diff --git a/assets/tsconfig.json b/assets/tsconfig.json new file mode 100644 index 0000000..0b48eff --- /dev/null +++ b/assets/tsconfig.json @@ -0,0 +1,30 @@ +{ + // https://esbuild.github.io/content-types/#tsconfig-json + "compilerOptions": { + // Keep in mind that ES6+ syntax to ES5 is not supported in esbuild yet. + "target": "es2016", + // Even when transpiling a single module, the TypeScript compiler actually + // parses imported files so it can tell whether an imported name is a type + // or a value. However, tools like esbuild compile each file in isolation so + // they can't tell if an imported name is a type or a value. + // https://esbuild.github.io/content-types/#isolated-modules + "isolatedModules": true, + // Disables legacy behavior around imports and makes TypeScript's type + // system compatible with ESM. + "esModuleInterop": true, + // Enables define semantics. In this mode, TypeScript class fields behave + // like normal JavaScript class fields. Field initializers do not trigger + // setters on the base class. + "useDefineForClassFields": true, + // If either of these options are enabled, esbuild will consider all code + // in all TypeScript files to be in strict mode and will prefix generated + // code with "use strict" unless the output format is set to esm (since all + // ESM files are automatically in strict mode). + "strict": true, + // Emit .js files with JSX changed to the equivalent React.createElement + // calls. It seems like the "react" value mirrors esbuild's native + // "transform" option, but it isn't obvious how these two relate from the + // documentation: https://esbuild.github.io/api/#jsx. + "jsx": "react" + } +} diff --git a/assets/vendor/heroicons/LICENSE.md b/assets/vendor/heroicons/LICENSE.md new file mode 100644 index 0000000..1ac3e40 --- /dev/null +++ b/assets/vendor/heroicons/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Refactoring UI Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/assets/vendor/heroicons/UPGRADE.md b/assets/vendor/heroicons/UPGRADE.md new file mode 100644 index 0000000..5a140b9 --- /dev/null +++ b/assets/vendor/heroicons/UPGRADE.md @@ -0,0 +1,6 @@ +You are running heroicons v2.0.16. To upgrade in place, you can run the following command, +where your `HERO_VSN` export is your desired version: + + export HERO_VSN="2.0.16" ; \ + curl -L "https://github.com/tailwindlabs/heroicons/archive/refs/tags/v${HERO_VSN}.tar.gz" | \ + tar -xvz --strip-components=1 heroicons-${HERO_VSN}/optimized diff --git a/assets/vendor/heroicons/optimized/20/solid/academic-cap.svg b/assets/vendor/heroicons/optimized/20/solid/academic-cap.svg new file mode 100644 index 0000000..bb980e6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/academic-cap.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/adjustments-horizontal.svg b/assets/vendor/heroicons/optimized/20/solid/adjustments-horizontal.svg new file mode 100644 index 0000000..13f9251 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/adjustments-horizontal.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/adjustments-vertical.svg b/assets/vendor/heroicons/optimized/20/solid/adjustments-vertical.svg new file mode 100644 index 0000000..6f75b6c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/adjustments-vertical.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/archive-box-arrow-down.svg b/assets/vendor/heroicons/optimized/20/solid/archive-box-arrow-down.svg new file mode 100644 index 0000000..5ccef9d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/archive-box-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/archive-box-x-mark.svg b/assets/vendor/heroicons/optimized/20/solid/archive-box-x-mark.svg new file mode 100644 index 0000000..73acbef --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/archive-box-x-mark.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/archive-box.svg b/assets/vendor/heroicons/optimized/20/solid/archive-box.svg new file mode 100644 index 0000000..2205ae8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/archive-box.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-down-circle.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-down-circle.svg new file mode 100644 index 0000000..bb21a89 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-down-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-down-left.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-down-left.svg new file mode 100644 index 0000000..f0c3624 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-down-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-down-on-square-stack.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-down-on-square-stack.svg new file mode 100644 index 0000000..d9c4db0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-down-on-square-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-down-on-square.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-down-on-square.svg new file mode 100644 index 0000000..4e8ee0f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-down-on-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-down-right.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-down-right.svg new file mode 100644 index 0000000..65cdada --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-down-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-down-tray.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-down-tray.svg new file mode 100644 index 0000000..5c3589d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-down-tray.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-down.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-down.svg new file mode 100644 index 0000000..8d577b1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-left-circle.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-left-circle.svg new file mode 100644 index 0000000..8cce243 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-left-circle.svg @@ -0,0 +1,10 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-left-on-rectangle.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-left-on-rectangle.svg new file mode 100644 index 0000000..74d0699 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-left-on-rectangle.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-left.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-left.svg new file mode 100644 index 0000000..c7efd30 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-long-down.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-long-down.svg new file mode 100644 index 0000000..d263061 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-long-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-long-left.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-long-left.svg new file mode 100644 index 0000000..297553c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-long-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-long-right.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-long-right.svg new file mode 100644 index 0000000..eea945e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-long-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-long-up.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-long-up.svg new file mode 100644 index 0000000..193ff97 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-long-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-path-rounded-square.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-path-rounded-square.svg new file mode 100644 index 0000000..7d3deb5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-path-rounded-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-path.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-path.svg new file mode 100644 index 0000000..1a31b4c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-path.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-right-circle.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-right-circle.svg new file mode 100644 index 0000000..4178da9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-right-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-right-on-rectangle.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-right-on-rectangle.svg new file mode 100644 index 0000000..27c4dff --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-right-on-rectangle.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-right.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-right.svg new file mode 100644 index 0000000..95cc517 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-small-down.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-small-down.svg new file mode 100644 index 0000000..b986c6c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-small-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-small-left.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-small-left.svg new file mode 100644 index 0000000..0cb7e71 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-small-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-small-right.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-small-right.svg new file mode 100644 index 0000000..16beb5e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-small-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-small-up.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-small-up.svg new file mode 100644 index 0000000..7fda0e5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-small-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-top-right-on-square.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-top-right-on-square.svg new file mode 100644 index 0000000..c93df54 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-top-right-on-square.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-trending-down.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-trending-down.svg new file mode 100644 index 0000000..5ec7c03 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-trending-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-trending-up.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-trending-up.svg new file mode 100644 index 0000000..67af2cc --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-trending-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-up-circle.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-up-circle.svg new file mode 100644 index 0000000..21d87f2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-up-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-up-left.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-up-left.svg new file mode 100644 index 0000000..fb50038 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-up-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-up-on-square-stack.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-up-on-square-stack.svg new file mode 100644 index 0000000..0df0ed2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-up-on-square-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-up-on-square.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-up-on-square.svg new file mode 100644 index 0000000..01f18a8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-up-on-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-up-right.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-up-right.svg new file mode 100644 index 0000000..4841916 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-up-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-up-tray.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-up-tray.svg new file mode 100644 index 0000000..832afe5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-up-tray.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-up.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-up.svg new file mode 100644 index 0000000..24b12b7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-down.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-down.svg new file mode 100644 index 0000000..6839676 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-left.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-left.svg new file mode 100644 index 0000000..6691aa4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-right.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-right.svg new file mode 100644 index 0000000..9a6e23b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-up.svg b/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-up.svg new file mode 100644 index 0000000..8e65bf8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrow-uturn-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrows-pointing-in.svg b/assets/vendor/heroicons/optimized/20/solid/arrows-pointing-in.svg new file mode 100644 index 0000000..6275807 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrows-pointing-in.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrows-pointing-out.svg b/assets/vendor/heroicons/optimized/20/solid/arrows-pointing-out.svg new file mode 100644 index 0000000..14e1bcd --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrows-pointing-out.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrows-right-left.svg b/assets/vendor/heroicons/optimized/20/solid/arrows-right-left.svg new file mode 100644 index 0000000..87616d3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrows-right-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/arrows-up-down.svg b/assets/vendor/heroicons/optimized/20/solid/arrows-up-down.svg new file mode 100644 index 0000000..b9c43d9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/arrows-up-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/at-symbol.svg b/assets/vendor/heroicons/optimized/20/solid/at-symbol.svg new file mode 100644 index 0000000..a9e960d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/at-symbol.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/backspace.svg b/assets/vendor/heroicons/optimized/20/solid/backspace.svg new file mode 100644 index 0000000..d3c1686 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/backspace.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/backward.svg b/assets/vendor/heroicons/optimized/20/solid/backward.svg new file mode 100644 index 0000000..8d341b2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/backward.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/banknotes.svg b/assets/vendor/heroicons/optimized/20/solid/banknotes.svg new file mode 100644 index 0000000..979c586 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/banknotes.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bars-2.svg b/assets/vendor/heroicons/optimized/20/solid/bars-2.svg new file mode 100644 index 0000000..4ce1e19 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bars-2.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bars-3-bottom-left.svg b/assets/vendor/heroicons/optimized/20/solid/bars-3-bottom-left.svg new file mode 100644 index 0000000..ebf785d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bars-3-bottom-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bars-3-bottom-right.svg b/assets/vendor/heroicons/optimized/20/solid/bars-3-bottom-right.svg new file mode 100644 index 0000000..af69c3d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bars-3-bottom-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bars-3-center-left.svg b/assets/vendor/heroicons/optimized/20/solid/bars-3-center-left.svg new file mode 100644 index 0000000..ee8a526 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bars-3-center-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bars-3.svg b/assets/vendor/heroicons/optimized/20/solid/bars-3.svg new file mode 100644 index 0000000..d164cfd --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bars-3.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bars-4.svg b/assets/vendor/heroicons/optimized/20/solid/bars-4.svg new file mode 100644 index 0000000..8e3f650 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bars-4.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bars-arrow-down.svg b/assets/vendor/heroicons/optimized/20/solid/bars-arrow-down.svg new file mode 100644 index 0000000..fd8f98d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bars-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bars-arrow-up.svg b/assets/vendor/heroicons/optimized/20/solid/bars-arrow-up.svg new file mode 100644 index 0000000..ba77dbe --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bars-arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/battery-0.svg b/assets/vendor/heroicons/optimized/20/solid/battery-0.svg new file mode 100644 index 0000000..2320f7e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/battery-0.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/battery-100.svg b/assets/vendor/heroicons/optimized/20/solid/battery-100.svg new file mode 100644 index 0000000..e04408f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/battery-100.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/battery-50.svg b/assets/vendor/heroicons/optimized/20/solid/battery-50.svg new file mode 100644 index 0000000..e5e8498 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/battery-50.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/beaker.svg b/assets/vendor/heroicons/optimized/20/solid/beaker.svg new file mode 100644 index 0000000..025c46b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/beaker.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bell-alert.svg b/assets/vendor/heroicons/optimized/20/solid/bell-alert.svg new file mode 100644 index 0000000..c84dc7c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bell-alert.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bell-slash.svg b/assets/vendor/heroicons/optimized/20/solid/bell-slash.svg new file mode 100644 index 0000000..a4296ac --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bell-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bell-snooze.svg b/assets/vendor/heroicons/optimized/20/solid/bell-snooze.svg new file mode 100644 index 0000000..ad4f8c5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bell-snooze.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bell.svg b/assets/vendor/heroicons/optimized/20/solid/bell.svg new file mode 100644 index 0000000..2a81bba --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bell.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bolt-slash.svg b/assets/vendor/heroicons/optimized/20/solid/bolt-slash.svg new file mode 100644 index 0000000..e4c2499 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bolt-slash.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bolt.svg b/assets/vendor/heroicons/optimized/20/solid/bolt.svg new file mode 100644 index 0000000..53f4fb9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bolt.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/book-open.svg b/assets/vendor/heroicons/optimized/20/solid/book-open.svg new file mode 100644 index 0000000..99cf0d4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/book-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bookmark-slash.svg b/assets/vendor/heroicons/optimized/20/solid/bookmark-slash.svg new file mode 100644 index 0000000..1442fd4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bookmark-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bookmark-square.svg b/assets/vendor/heroicons/optimized/20/solid/bookmark-square.svg new file mode 100644 index 0000000..849c425 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bookmark-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bookmark.svg b/assets/vendor/heroicons/optimized/20/solid/bookmark.svg new file mode 100644 index 0000000..a3e68de --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bookmark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/briefcase.svg b/assets/vendor/heroicons/optimized/20/solid/briefcase.svg new file mode 100644 index 0000000..275f4b3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/briefcase.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/bug-ant.svg b/assets/vendor/heroicons/optimized/20/solid/bug-ant.svg new file mode 100644 index 0000000..bc86615 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/bug-ant.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/building-library.svg b/assets/vendor/heroicons/optimized/20/solid/building-library.svg new file mode 100644 index 0000000..610bce7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/building-library.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/building-office-2.svg b/assets/vendor/heroicons/optimized/20/solid/building-office-2.svg new file mode 100644 index 0000000..b25c33d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/building-office-2.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/building-office.svg b/assets/vendor/heroicons/optimized/20/solid/building-office.svg new file mode 100644 index 0000000..8fb3fab --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/building-office.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/building-storefront.svg b/assets/vendor/heroicons/optimized/20/solid/building-storefront.svg new file mode 100644 index 0000000..0d7da7c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/building-storefront.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cake.svg b/assets/vendor/heroicons/optimized/20/solid/cake.svg new file mode 100644 index 0000000..c58d3b9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cake.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/calculator.svg b/assets/vendor/heroicons/optimized/20/solid/calculator.svg new file mode 100644 index 0000000..cc26160 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/calculator.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/calendar-days.svg b/assets/vendor/heroicons/optimized/20/solid/calendar-days.svg new file mode 100644 index 0000000..ded2318 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/calendar-days.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/calendar.svg b/assets/vendor/heroicons/optimized/20/solid/calendar.svg new file mode 100644 index 0000000..dea374d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/calendar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/camera.svg b/assets/vendor/heroicons/optimized/20/solid/camera.svg new file mode 100644 index 0000000..17356ae --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/camera.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chart-bar-square.svg b/assets/vendor/heroicons/optimized/20/solid/chart-bar-square.svg new file mode 100644 index 0000000..6d5b976 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chart-bar-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chart-bar.svg b/assets/vendor/heroicons/optimized/20/solid/chart-bar.svg new file mode 100644 index 0000000..c496112 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chart-bar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chart-pie.svg b/assets/vendor/heroicons/optimized/20/solid/chart-pie.svg new file mode 100644 index 0000000..37d18c9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chart-pie.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chat-bubble-bottom-center-text.svg b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-bottom-center-text.svg new file mode 100644 index 0000000..fee91be --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-bottom-center-text.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chat-bubble-bottom-center.svg b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-bottom-center.svg new file mode 100644 index 0000000..24de840 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-bottom-center.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chat-bubble-left-ellipsis.svg b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-left-ellipsis.svg new file mode 100644 index 0000000..7e2b06c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-left-ellipsis.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chat-bubble-left-right.svg b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-left-right.svg new file mode 100644 index 0000000..fceca7f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-left-right.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chat-bubble-left.svg b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-left.svg new file mode 100644 index 0000000..cd706f9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chat-bubble-oval-left-ellipsis.svg b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-oval-left-ellipsis.svg new file mode 100644 index 0000000..88d9c32 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-oval-left-ellipsis.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chat-bubble-oval-left.svg b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-oval-left.svg new file mode 100644 index 0000000..f3b3883 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chat-bubble-oval-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/check-badge.svg b/assets/vendor/heroicons/optimized/20/solid/check-badge.svg new file mode 100644 index 0000000..b13190c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/check-badge.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/check-circle.svg b/assets/vendor/heroicons/optimized/20/solid/check-circle.svg new file mode 100644 index 0000000..dbbc905 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/check-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/check.svg b/assets/vendor/heroicons/optimized/20/solid/check.svg new file mode 100644 index 0000000..d1956aa --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/check.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chevron-double-down.svg b/assets/vendor/heroicons/optimized/20/solid/chevron-double-down.svg new file mode 100644 index 0000000..3c65193 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chevron-double-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chevron-double-left.svg b/assets/vendor/heroicons/optimized/20/solid/chevron-double-left.svg new file mode 100644 index 0000000..a41e615 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chevron-double-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chevron-double-right.svg b/assets/vendor/heroicons/optimized/20/solid/chevron-double-right.svg new file mode 100644 index 0000000..d78c932 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chevron-double-right.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chevron-double-up.svg b/assets/vendor/heroicons/optimized/20/solid/chevron-double-up.svg new file mode 100644 index 0000000..f7ac668 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chevron-double-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chevron-down.svg b/assets/vendor/heroicons/optimized/20/solid/chevron-down.svg new file mode 100644 index 0000000..6b63dfb --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chevron-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chevron-left.svg b/assets/vendor/heroicons/optimized/20/solid/chevron-left.svg new file mode 100644 index 0000000..b484a40 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chevron-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chevron-right.svg b/assets/vendor/heroicons/optimized/20/solid/chevron-right.svg new file mode 100644 index 0000000..9ce3745 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chevron-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chevron-up-down.svg b/assets/vendor/heroicons/optimized/20/solid/chevron-up-down.svg new file mode 100644 index 0000000..2499bb1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chevron-up-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/chevron-up.svg b/assets/vendor/heroicons/optimized/20/solid/chevron-up.svg new file mode 100644 index 0000000..8aaaa1f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/chevron-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/circle-stack.svg b/assets/vendor/heroicons/optimized/20/solid/circle-stack.svg new file mode 100644 index 0000000..fae2090 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/circle-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/clipboard-document-check.svg b/assets/vendor/heroicons/optimized/20/solid/clipboard-document-check.svg new file mode 100644 index 0000000..2447012 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/clipboard-document-check.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/clipboard-document-list.svg b/assets/vendor/heroicons/optimized/20/solid/clipboard-document-list.svg new file mode 100644 index 0000000..a78b24c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/clipboard-document-list.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/clipboard-document.svg b/assets/vendor/heroicons/optimized/20/solid/clipboard-document.svg new file mode 100644 index 0000000..f29a433 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/clipboard-document.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/clipboard.svg b/assets/vendor/heroicons/optimized/20/solid/clipboard.svg new file mode 100644 index 0000000..3b2a7ff --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/clipboard.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/clock.svg b/assets/vendor/heroicons/optimized/20/solid/clock.svg new file mode 100644 index 0000000..d72dd62 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/clock.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cloud-arrow-down.svg b/assets/vendor/heroicons/optimized/20/solid/cloud-arrow-down.svg new file mode 100644 index 0000000..cb32c04 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cloud-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cloud-arrow-up.svg b/assets/vendor/heroicons/optimized/20/solid/cloud-arrow-up.svg new file mode 100644 index 0000000..06b2c4a --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cloud-arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cloud.svg b/assets/vendor/heroicons/optimized/20/solid/cloud.svg new file mode 100644 index 0000000..c5eb0c8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cloud.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/code-bracket-square.svg b/assets/vendor/heroicons/optimized/20/solid/code-bracket-square.svg new file mode 100644 index 0000000..e4e3530 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/code-bracket-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/code-bracket.svg b/assets/vendor/heroicons/optimized/20/solid/code-bracket.svg new file mode 100644 index 0000000..4adfd32 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/code-bracket.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cog-6-tooth.svg b/assets/vendor/heroicons/optimized/20/solid/cog-6-tooth.svg new file mode 100644 index 0000000..435032f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cog-6-tooth.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cog-8-tooth.svg b/assets/vendor/heroicons/optimized/20/solid/cog-8-tooth.svg new file mode 100644 index 0000000..0450feb --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cog-8-tooth.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cog.svg b/assets/vendor/heroicons/optimized/20/solid/cog.svg new file mode 100644 index 0000000..10f43b5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cog.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/command-line.svg b/assets/vendor/heroicons/optimized/20/solid/command-line.svg new file mode 100644 index 0000000..59cf34c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/command-line.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/computer-desktop.svg b/assets/vendor/heroicons/optimized/20/solid/computer-desktop.svg new file mode 100644 index 0000000..4eeac1b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/computer-desktop.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cpu-chip.svg b/assets/vendor/heroicons/optimized/20/solid/cpu-chip.svg new file mode 100644 index 0000000..f7c49b4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cpu-chip.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/credit-card.svg b/assets/vendor/heroicons/optimized/20/solid/credit-card.svg new file mode 100644 index 0000000..d650fea --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/credit-card.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cube-transparent.svg b/assets/vendor/heroicons/optimized/20/solid/cube-transparent.svg new file mode 100644 index 0000000..bfe9dae --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cube-transparent.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cube.svg b/assets/vendor/heroicons/optimized/20/solid/cube.svg new file mode 100644 index 0000000..c3436ed --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cube.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/currency-bangladeshi.svg b/assets/vendor/heroicons/optimized/20/solid/currency-bangladeshi.svg new file mode 100644 index 0000000..2a27260 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/currency-bangladeshi.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/currency-dollar.svg b/assets/vendor/heroicons/optimized/20/solid/currency-dollar.svg new file mode 100644 index 0000000..1b00de7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/currency-dollar.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/currency-euro.svg b/assets/vendor/heroicons/optimized/20/solid/currency-euro.svg new file mode 100644 index 0000000..43fda8e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/currency-euro.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/currency-pound.svg b/assets/vendor/heroicons/optimized/20/solid/currency-pound.svg new file mode 100644 index 0000000..9bc57fe --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/currency-pound.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/currency-rupee.svg b/assets/vendor/heroicons/optimized/20/solid/currency-rupee.svg new file mode 100644 index 0000000..67da528 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/currency-rupee.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/currency-yen.svg b/assets/vendor/heroicons/optimized/20/solid/currency-yen.svg new file mode 100644 index 0000000..68fca06 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/currency-yen.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cursor-arrow-rays.svg b/assets/vendor/heroicons/optimized/20/solid/cursor-arrow-rays.svg new file mode 100644 index 0000000..d2d36a7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cursor-arrow-rays.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/cursor-arrow-ripple.svg b/assets/vendor/heroicons/optimized/20/solid/cursor-arrow-ripple.svg new file mode 100644 index 0000000..89ccfda --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/cursor-arrow-ripple.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/device-phone-mobile.svg b/assets/vendor/heroicons/optimized/20/solid/device-phone-mobile.svg new file mode 100644 index 0000000..68fac4f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/device-phone-mobile.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/device-tablet.svg b/assets/vendor/heroicons/optimized/20/solid/device-tablet.svg new file mode 100644 index 0000000..2490e1f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/device-tablet.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document-arrow-down.svg b/assets/vendor/heroicons/optimized/20/solid/document-arrow-down.svg new file mode 100644 index 0000000..a51461d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document-arrow-up.svg b/assets/vendor/heroicons/optimized/20/solid/document-arrow-up.svg new file mode 100644 index 0000000..d55aacd --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document-arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document-chart-bar.svg b/assets/vendor/heroicons/optimized/20/solid/document-chart-bar.svg new file mode 100644 index 0000000..8d5f60a --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document-chart-bar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document-check.svg b/assets/vendor/heroicons/optimized/20/solid/document-check.svg new file mode 100644 index 0000000..b28da14 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document-check.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document-duplicate.svg b/assets/vendor/heroicons/optimized/20/solid/document-duplicate.svg new file mode 100644 index 0000000..64768cf --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document-duplicate.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document-magnifying-glass.svg b/assets/vendor/heroicons/optimized/20/solid/document-magnifying-glass.svg new file mode 100644 index 0000000..0dc8eba --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document-magnifying-glass.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document-minus.svg b/assets/vendor/heroicons/optimized/20/solid/document-minus.svg new file mode 100644 index 0000000..bf7af06 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document-plus.svg b/assets/vendor/heroicons/optimized/20/solid/document-plus.svg new file mode 100644 index 0000000..133cc82 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document-text.svg b/assets/vendor/heroicons/optimized/20/solid/document-text.svg new file mode 100644 index 0000000..f7d91ba --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document-text.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/document.svg b/assets/vendor/heroicons/optimized/20/solid/document.svg new file mode 100644 index 0000000..aa7070c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/document.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/ellipsis-horizontal-circle.svg b/assets/vendor/heroicons/optimized/20/solid/ellipsis-horizontal-circle.svg new file mode 100644 index 0000000..799cf88 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/ellipsis-horizontal-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/ellipsis-horizontal.svg b/assets/vendor/heroicons/optimized/20/solid/ellipsis-horizontal.svg new file mode 100644 index 0000000..a82211c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/ellipsis-horizontal.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/ellipsis-vertical.svg b/assets/vendor/heroicons/optimized/20/solid/ellipsis-vertical.svg new file mode 100644 index 0000000..ac9c83f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/ellipsis-vertical.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/envelope-open.svg b/assets/vendor/heroicons/optimized/20/solid/envelope-open.svg new file mode 100644 index 0000000..a60035b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/envelope-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/envelope.svg b/assets/vendor/heroicons/optimized/20/solid/envelope.svg new file mode 100644 index 0000000..b582b8f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/envelope.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/exclamation-circle.svg b/assets/vendor/heroicons/optimized/20/solid/exclamation-circle.svg new file mode 100644 index 0000000..3cf5b59 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/exclamation-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/exclamation-triangle.svg b/assets/vendor/heroicons/optimized/20/solid/exclamation-triangle.svg new file mode 100644 index 0000000..a6cc02f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/exclamation-triangle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/eye-dropper.svg b/assets/vendor/heroicons/optimized/20/solid/eye-dropper.svg new file mode 100644 index 0000000..cce0833 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/eye-dropper.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/eye-slash.svg b/assets/vendor/heroicons/optimized/20/solid/eye-slash.svg new file mode 100644 index 0000000..cc2e222 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/eye-slash.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/eye.svg b/assets/vendor/heroicons/optimized/20/solid/eye.svg new file mode 100644 index 0000000..b17cec9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/eye.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/face-frown.svg b/assets/vendor/heroicons/optimized/20/solid/face-frown.svg new file mode 100644 index 0000000..8cbb3b3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/face-frown.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/face-smile.svg b/assets/vendor/heroicons/optimized/20/solid/face-smile.svg new file mode 100644 index 0000000..434c83e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/face-smile.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/film.svg b/assets/vendor/heroicons/optimized/20/solid/film.svg new file mode 100644 index 0000000..9573445 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/film.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/finger-print.svg b/assets/vendor/heroicons/optimized/20/solid/finger-print.svg new file mode 100644 index 0000000..409d67e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/finger-print.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/fire.svg b/assets/vendor/heroicons/optimized/20/solid/fire.svg new file mode 100644 index 0000000..88fb85e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/fire.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/flag.svg b/assets/vendor/heroicons/optimized/20/solid/flag.svg new file mode 100644 index 0000000..b790f05 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/flag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/folder-arrow-down.svg b/assets/vendor/heroicons/optimized/20/solid/folder-arrow-down.svg new file mode 100644 index 0000000..4c2a915 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/folder-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/folder-minus.svg b/assets/vendor/heroicons/optimized/20/solid/folder-minus.svg new file mode 100644 index 0000000..248388b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/folder-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/folder-open.svg b/assets/vendor/heroicons/optimized/20/solid/folder-open.svg new file mode 100644 index 0000000..79d245e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/folder-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/folder-plus.svg b/assets/vendor/heroicons/optimized/20/solid/folder-plus.svg new file mode 100644 index 0000000..0780a54 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/folder-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/folder.svg b/assets/vendor/heroicons/optimized/20/solid/folder.svg new file mode 100644 index 0000000..c92e0ff --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/folder.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/forward.svg b/assets/vendor/heroicons/optimized/20/solid/forward.svg new file mode 100644 index 0000000..11fd161 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/forward.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/funnel.svg b/assets/vendor/heroicons/optimized/20/solid/funnel.svg new file mode 100644 index 0000000..af3078f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/funnel.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/gif.svg b/assets/vendor/heroicons/optimized/20/solid/gif.svg new file mode 100644 index 0000000..ff1cd38 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/gif.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/gift-top.svg b/assets/vendor/heroicons/optimized/20/solid/gift-top.svg new file mode 100644 index 0000000..d650d04 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/gift-top.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/gift.svg b/assets/vendor/heroicons/optimized/20/solid/gift.svg new file mode 100644 index 0000000..90362ab --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/gift.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/globe-alt.svg b/assets/vendor/heroicons/optimized/20/solid/globe-alt.svg new file mode 100644 index 0000000..c8ff599 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/globe-alt.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/globe-americas.svg b/assets/vendor/heroicons/optimized/20/solid/globe-americas.svg new file mode 100644 index 0000000..7de8723 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/globe-americas.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/globe-asia-australia.svg b/assets/vendor/heroicons/optimized/20/solid/globe-asia-australia.svg new file mode 100644 index 0000000..b3da4dd --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/globe-asia-australia.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/globe-europe-africa.svg b/assets/vendor/heroicons/optimized/20/solid/globe-europe-africa.svg new file mode 100644 index 0000000..92db1e1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/globe-europe-africa.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/hand-raised.svg b/assets/vendor/heroicons/optimized/20/solid/hand-raised.svg new file mode 100644 index 0000000..caa27db --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/hand-raised.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/hand-thumb-down.svg b/assets/vendor/heroicons/optimized/20/solid/hand-thumb-down.svg new file mode 100644 index 0000000..06ffbe0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/hand-thumb-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/hand-thumb-up.svg b/assets/vendor/heroicons/optimized/20/solid/hand-thumb-up.svg new file mode 100644 index 0000000..3d67d98 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/hand-thumb-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/hashtag.svg b/assets/vendor/heroicons/optimized/20/solid/hashtag.svg new file mode 100644 index 0000000..29c9f10 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/hashtag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/heart.svg b/assets/vendor/heroicons/optimized/20/solid/heart.svg new file mode 100644 index 0000000..96e63d9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/heart.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/home-modern.svg b/assets/vendor/heroicons/optimized/20/solid/home-modern.svg new file mode 100644 index 0000000..95033a3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/home-modern.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/home.svg b/assets/vendor/heroicons/optimized/20/solid/home.svg new file mode 100644 index 0000000..ae2acdc --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/home.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/identification.svg b/assets/vendor/heroicons/optimized/20/solid/identification.svg new file mode 100644 index 0000000..7a8338c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/identification.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/inbox-arrow-down.svg b/assets/vendor/heroicons/optimized/20/solid/inbox-arrow-down.svg new file mode 100644 index 0000000..37f464e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/inbox-arrow-down.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/inbox-stack.svg b/assets/vendor/heroicons/optimized/20/solid/inbox-stack.svg new file mode 100644 index 0000000..b8f22d1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/inbox-stack.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/inbox.svg b/assets/vendor/heroicons/optimized/20/solid/inbox.svg new file mode 100644 index 0000000..87ae30d --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/inbox.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/information-circle.svg b/assets/vendor/heroicons/optimized/20/solid/information-circle.svg new file mode 100644 index 0000000..25ac644 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/information-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/key.svg b/assets/vendor/heroicons/optimized/20/solid/key.svg new file mode 100644 index 0000000..97e8ad8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/key.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/language.svg b/assets/vendor/heroicons/optimized/20/solid/language.svg new file mode 100644 index 0000000..e7524be --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/language.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/lifebuoy.svg b/assets/vendor/heroicons/optimized/20/solid/lifebuoy.svg new file mode 100644 index 0000000..79134c1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/lifebuoy.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/light-bulb.svg b/assets/vendor/heroicons/optimized/20/solid/light-bulb.svg new file mode 100644 index 0000000..415e374 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/light-bulb.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/link.svg b/assets/vendor/heroicons/optimized/20/solid/link.svg new file mode 100644 index 0000000..e820413 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/link.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/list-bullet.svg b/assets/vendor/heroicons/optimized/20/solid/list-bullet.svg new file mode 100644 index 0000000..3637934 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/list-bullet.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/lock-closed.svg b/assets/vendor/heroicons/optimized/20/solid/lock-closed.svg new file mode 100644 index 0000000..e7e1dc1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/lock-closed.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/lock-open.svg b/assets/vendor/heroicons/optimized/20/solid/lock-open.svg new file mode 100644 index 0000000..2ac4b14 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/lock-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/magnifying-glass-circle.svg b/assets/vendor/heroicons/optimized/20/solid/magnifying-glass-circle.svg new file mode 100644 index 0000000..e025769 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/magnifying-glass-circle.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/magnifying-glass-minus.svg b/assets/vendor/heroicons/optimized/20/solid/magnifying-glass-minus.svg new file mode 100644 index 0000000..a8fb7a2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/magnifying-glass-minus.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/magnifying-glass-plus.svg b/assets/vendor/heroicons/optimized/20/solid/magnifying-glass-plus.svg new file mode 100644 index 0000000..e55fc4c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/magnifying-glass-plus.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/magnifying-glass.svg b/assets/vendor/heroicons/optimized/20/solid/magnifying-glass.svg new file mode 100644 index 0000000..d90520a --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/magnifying-glass.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/map-pin.svg b/assets/vendor/heroicons/optimized/20/solid/map-pin.svg new file mode 100644 index 0000000..7adc8a9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/map-pin.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/map.svg b/assets/vendor/heroicons/optimized/20/solid/map.svg new file mode 100644 index 0000000..7e25c7b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/map.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/megaphone.svg b/assets/vendor/heroicons/optimized/20/solid/megaphone.svg new file mode 100644 index 0000000..cf73785 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/megaphone.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/microphone.svg b/assets/vendor/heroicons/optimized/20/solid/microphone.svg new file mode 100644 index 0000000..635ded0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/microphone.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/minus-circle.svg b/assets/vendor/heroicons/optimized/20/solid/minus-circle.svg new file mode 100644 index 0000000..5c361e2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/minus-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/minus-small.svg b/assets/vendor/heroicons/optimized/20/solid/minus-small.svg new file mode 100644 index 0000000..4e041d3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/minus-small.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/minus.svg b/assets/vendor/heroicons/optimized/20/solid/minus.svg new file mode 100644 index 0000000..6d8dd24 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/moon.svg b/assets/vendor/heroicons/optimized/20/solid/moon.svg new file mode 100644 index 0000000..b592110 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/moon.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/musical-note.svg b/assets/vendor/heroicons/optimized/20/solid/musical-note.svg new file mode 100644 index 0000000..c7f0aa8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/musical-note.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/newspaper.svg b/assets/vendor/heroicons/optimized/20/solid/newspaper.svg new file mode 100644 index 0000000..be785e3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/newspaper.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/no-symbol.svg b/assets/vendor/heroicons/optimized/20/solid/no-symbol.svg new file mode 100644 index 0000000..8463da2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/no-symbol.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/paint-brush.svg b/assets/vendor/heroicons/optimized/20/solid/paint-brush.svg new file mode 100644 index 0000000..c2db8c0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/paint-brush.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/paper-airplane.svg b/assets/vendor/heroicons/optimized/20/solid/paper-airplane.svg new file mode 100644 index 0000000..35315ad --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/paper-airplane.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/paper-clip.svg b/assets/vendor/heroicons/optimized/20/solid/paper-clip.svg new file mode 100644 index 0000000..b38155e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/paper-clip.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/pause-circle.svg b/assets/vendor/heroicons/optimized/20/solid/pause-circle.svg new file mode 100644 index 0000000..732530a --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/pause-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/pause.svg b/assets/vendor/heroicons/optimized/20/solid/pause.svg new file mode 100644 index 0000000..b6544ba --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/pause.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/pencil-square.svg b/assets/vendor/heroicons/optimized/20/solid/pencil-square.svg new file mode 100644 index 0000000..129ee44 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/pencil-square.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/pencil.svg b/assets/vendor/heroicons/optimized/20/solid/pencil.svg new file mode 100644 index 0000000..fa50498 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/pencil.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/phone-arrow-down-left.svg b/assets/vendor/heroicons/optimized/20/solid/phone-arrow-down-left.svg new file mode 100644 index 0000000..feeb636 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/phone-arrow-down-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/phone-arrow-up-right.svg b/assets/vendor/heroicons/optimized/20/solid/phone-arrow-up-right.svg new file mode 100644 index 0000000..e1d9331 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/phone-arrow-up-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/phone-x-mark.svg b/assets/vendor/heroicons/optimized/20/solid/phone-x-mark.svg new file mode 100644 index 0000000..6f1fd36 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/phone-x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/phone.svg b/assets/vendor/heroicons/optimized/20/solid/phone.svg new file mode 100644 index 0000000..5fd57fc --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/phone.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/photo.svg b/assets/vendor/heroicons/optimized/20/solid/photo.svg new file mode 100644 index 0000000..db063d3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/photo.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/play-circle.svg b/assets/vendor/heroicons/optimized/20/solid/play-circle.svg new file mode 100644 index 0000000..d1587b7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/play-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/play-pause.svg b/assets/vendor/heroicons/optimized/20/solid/play-pause.svg new file mode 100644 index 0000000..cdfa63b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/play-pause.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/play.svg b/assets/vendor/heroicons/optimized/20/solid/play.svg new file mode 100644 index 0000000..2cd53e1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/play.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/plus-circle.svg b/assets/vendor/heroicons/optimized/20/solid/plus-circle.svg new file mode 100644 index 0000000..3925a14 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/plus-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/plus-small.svg b/assets/vendor/heroicons/optimized/20/solid/plus-small.svg new file mode 100644 index 0000000..eb22598 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/plus-small.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/plus.svg b/assets/vendor/heroicons/optimized/20/solid/plus.svg new file mode 100644 index 0000000..218ab93 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/power.svg b/assets/vendor/heroicons/optimized/20/solid/power.svg new file mode 100644 index 0000000..1603820 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/power.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/presentation-chart-bar.svg b/assets/vendor/heroicons/optimized/20/solid/presentation-chart-bar.svg new file mode 100644 index 0000000..e1a4242 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/presentation-chart-bar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/presentation-chart-line.svg b/assets/vendor/heroicons/optimized/20/solid/presentation-chart-line.svg new file mode 100644 index 0000000..2e9281f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/presentation-chart-line.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/printer.svg b/assets/vendor/heroicons/optimized/20/solid/printer.svg new file mode 100644 index 0000000..81d93dc --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/printer.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/puzzle-piece.svg b/assets/vendor/heroicons/optimized/20/solid/puzzle-piece.svg new file mode 100644 index 0000000..e5329e5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/puzzle-piece.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/qr-code.svg b/assets/vendor/heroicons/optimized/20/solid/qr-code.svg new file mode 100644 index 0000000..d5daf2a --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/qr-code.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/question-mark-circle.svg b/assets/vendor/heroicons/optimized/20/solid/question-mark-circle.svg new file mode 100644 index 0000000..893dab5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/question-mark-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/queue-list.svg b/assets/vendor/heroicons/optimized/20/solid/queue-list.svg new file mode 100644 index 0000000..91f5b57 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/queue-list.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/radio.svg b/assets/vendor/heroicons/optimized/20/solid/radio.svg new file mode 100644 index 0000000..9a12c8b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/radio.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/receipt-percent.svg b/assets/vendor/heroicons/optimized/20/solid/receipt-percent.svg new file mode 100644 index 0000000..1a8f254 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/receipt-percent.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/receipt-refund.svg b/assets/vendor/heroicons/optimized/20/solid/receipt-refund.svg new file mode 100644 index 0000000..a134c56 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/receipt-refund.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/rectangle-group.svg b/assets/vendor/heroicons/optimized/20/solid/rectangle-group.svg new file mode 100644 index 0000000..599d5f4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/rectangle-group.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/rectangle-stack.svg b/assets/vendor/heroicons/optimized/20/solid/rectangle-stack.svg new file mode 100644 index 0000000..94b5654 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/rectangle-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/rocket-launch.svg b/assets/vendor/heroicons/optimized/20/solid/rocket-launch.svg new file mode 100644 index 0000000..b97e89e --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/rocket-launch.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/rss.svg b/assets/vendor/heroicons/optimized/20/solid/rss.svg new file mode 100644 index 0000000..538ae9a --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/rss.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/scale.svg b/assets/vendor/heroicons/optimized/20/solid/scale.svg new file mode 100644 index 0000000..60b0ed9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/scale.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/scissors.svg b/assets/vendor/heroicons/optimized/20/solid/scissors.svg new file mode 100644 index 0000000..3122658 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/scissors.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/server-stack.svg b/assets/vendor/heroicons/optimized/20/solid/server-stack.svg new file mode 100644 index 0000000..2247dbe --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/server-stack.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/server.svg b/assets/vendor/heroicons/optimized/20/solid/server.svg new file mode 100644 index 0000000..6c8899b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/server.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/share.svg b/assets/vendor/heroicons/optimized/20/solid/share.svg new file mode 100644 index 0000000..e5aafee --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/share.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/shield-check.svg b/assets/vendor/heroicons/optimized/20/solid/shield-check.svg new file mode 100644 index 0000000..a8572d4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/shield-check.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/shield-exclamation.svg b/assets/vendor/heroicons/optimized/20/solid/shield-exclamation.svg new file mode 100644 index 0000000..8562de4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/shield-exclamation.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/shopping-bag.svg b/assets/vendor/heroicons/optimized/20/solid/shopping-bag.svg new file mode 100644 index 0000000..e70c815 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/shopping-bag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/shopping-cart.svg b/assets/vendor/heroicons/optimized/20/solid/shopping-cart.svg new file mode 100644 index 0000000..855a631 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/shopping-cart.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/signal-slash.svg b/assets/vendor/heroicons/optimized/20/solid/signal-slash.svg new file mode 100644 index 0000000..822179a --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/signal-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/signal.svg b/assets/vendor/heroicons/optimized/20/solid/signal.svg new file mode 100644 index 0000000..7539204 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/signal.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/sparkles.svg b/assets/vendor/heroicons/optimized/20/solid/sparkles.svg new file mode 100644 index 0000000..a247c94 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/sparkles.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/speaker-wave.svg b/assets/vendor/heroicons/optimized/20/solid/speaker-wave.svg new file mode 100644 index 0000000..2e6b3e0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/speaker-wave.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/speaker-x-mark.svg b/assets/vendor/heroicons/optimized/20/solid/speaker-x-mark.svg new file mode 100644 index 0000000..0c39f61 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/speaker-x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/square-2-stack.svg b/assets/vendor/heroicons/optimized/20/solid/square-2-stack.svg new file mode 100644 index 0000000..177d43a --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/square-2-stack.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/square-3-stack-3d.svg b/assets/vendor/heroicons/optimized/20/solid/square-3-stack-3d.svg new file mode 100644 index 0000000..7d6b44f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/square-3-stack-3d.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/squares-2x2.svg b/assets/vendor/heroicons/optimized/20/solid/squares-2x2.svg new file mode 100644 index 0000000..47c1817 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/squares-2x2.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/squares-plus.svg b/assets/vendor/heroicons/optimized/20/solid/squares-plus.svg new file mode 100644 index 0000000..019eb37 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/squares-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/star.svg b/assets/vendor/heroicons/optimized/20/solid/star.svg new file mode 100644 index 0000000..a870365 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/star.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/stop-circle.svg b/assets/vendor/heroicons/optimized/20/solid/stop-circle.svg new file mode 100644 index 0000000..9167a64 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/stop-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/stop.svg b/assets/vendor/heroicons/optimized/20/solid/stop.svg new file mode 100644 index 0000000..b0e40db --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/stop.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/sun.svg b/assets/vendor/heroicons/optimized/20/solid/sun.svg new file mode 100644 index 0000000..449e36c --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/sun.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/swatch.svg b/assets/vendor/heroicons/optimized/20/solid/swatch.svg new file mode 100644 index 0000000..1089548 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/swatch.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/table-cells.svg b/assets/vendor/heroicons/optimized/20/solid/table-cells.svg new file mode 100644 index 0000000..aa8eacd --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/table-cells.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/tag.svg b/assets/vendor/heroicons/optimized/20/solid/tag.svg new file mode 100644 index 0000000..d6c9c8b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/tag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/ticket.svg b/assets/vendor/heroicons/optimized/20/solid/ticket.svg new file mode 100644 index 0000000..7d04b01 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/ticket.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/trash.svg b/assets/vendor/heroicons/optimized/20/solid/trash.svg new file mode 100644 index 0000000..16bd5c6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/trash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/trophy.svg b/assets/vendor/heroicons/optimized/20/solid/trophy.svg new file mode 100644 index 0000000..e2ebdaf --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/trophy.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/truck.svg b/assets/vendor/heroicons/optimized/20/solid/truck.svg new file mode 100644 index 0000000..a50085a --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/truck.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/tv.svg b/assets/vendor/heroicons/optimized/20/solid/tv.svg new file mode 100644 index 0000000..b0c7209 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/tv.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/user-circle.svg b/assets/vendor/heroicons/optimized/20/solid/user-circle.svg new file mode 100644 index 0000000..abd6c98 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/user-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/user-group.svg b/assets/vendor/heroicons/optimized/20/solid/user-group.svg new file mode 100644 index 0000000..dc83736 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/user-group.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/user-minus.svg b/assets/vendor/heroicons/optimized/20/solid/user-minus.svg new file mode 100644 index 0000000..b092b77 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/user-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/user-plus.svg b/assets/vendor/heroicons/optimized/20/solid/user-plus.svg new file mode 100644 index 0000000..afd5965 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/user-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/user.svg b/assets/vendor/heroicons/optimized/20/solid/user.svg new file mode 100644 index 0000000..4d9b2ee --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/user.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/users.svg b/assets/vendor/heroicons/optimized/20/solid/users.svg new file mode 100644 index 0000000..43526a6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/users.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/variable.svg b/assets/vendor/heroicons/optimized/20/solid/variable.svg new file mode 100644 index 0000000..3460817 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/variable.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/video-camera-slash.svg b/assets/vendor/heroicons/optimized/20/solid/video-camera-slash.svg new file mode 100644 index 0000000..95b9413 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/video-camera-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/video-camera.svg b/assets/vendor/heroicons/optimized/20/solid/video-camera.svg new file mode 100644 index 0000000..4d22f16 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/video-camera.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/view-columns.svg b/assets/vendor/heroicons/optimized/20/solid/view-columns.svg new file mode 100644 index 0000000..1111492 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/view-columns.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/viewfinder-circle.svg b/assets/vendor/heroicons/optimized/20/solid/viewfinder-circle.svg new file mode 100644 index 0000000..5cfe9fb --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/viewfinder-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/wallet.svg b/assets/vendor/heroicons/optimized/20/solid/wallet.svg new file mode 100644 index 0000000..6ce8050 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/wallet.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/wifi.svg b/assets/vendor/heroicons/optimized/20/solid/wifi.svg new file mode 100644 index 0000000..f3c7118 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/wifi.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/window.svg b/assets/vendor/heroicons/optimized/20/solid/window.svg new file mode 100644 index 0000000..9110a2b --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/window.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/wrench-screwdriver.svg b/assets/vendor/heroicons/optimized/20/solid/wrench-screwdriver.svg new file mode 100644 index 0000000..2ad879f --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/wrench-screwdriver.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/wrench.svg b/assets/vendor/heroicons/optimized/20/solid/wrench.svg new file mode 100644 index 0000000..2643e00 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/wrench.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/x-circle.svg b/assets/vendor/heroicons/optimized/20/solid/x-circle.svg new file mode 100644 index 0000000..a660014 --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/x-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/20/solid/x-mark.svg b/assets/vendor/heroicons/optimized/20/solid/x-mark.svg new file mode 100644 index 0000000..0635bac --- /dev/null +++ b/assets/vendor/heroicons/optimized/20/solid/x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/academic-cap.svg b/assets/vendor/heroicons/optimized/24/outline/academic-cap.svg new file mode 100644 index 0000000..fc18107 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/academic-cap.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/adjustments-horizontal.svg b/assets/vendor/heroicons/optimized/24/outline/adjustments-horizontal.svg new file mode 100644 index 0000000..e859e5d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/adjustments-horizontal.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/adjustments-vertical.svg b/assets/vendor/heroicons/optimized/24/outline/adjustments-vertical.svg new file mode 100644 index 0000000..6153809 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/adjustments-vertical.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/archive-box-arrow-down.svg b/assets/vendor/heroicons/optimized/24/outline/archive-box-arrow-down.svg new file mode 100644 index 0000000..1a0a830 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/archive-box-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/archive-box-x-mark.svg b/assets/vendor/heroicons/optimized/24/outline/archive-box-x-mark.svg new file mode 100644 index 0000000..49bd087 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/archive-box-x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/archive-box.svg b/assets/vendor/heroicons/optimized/24/outline/archive-box.svg new file mode 100644 index 0000000..704f353 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/archive-box.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-down-circle.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-down-circle.svg new file mode 100644 index 0000000..248b0e8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-down-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-down-left.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-down-left.svg new file mode 100644 index 0000000..262b5ff --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-down-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-down-on-square-stack.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-down-on-square-stack.svg new file mode 100644 index 0000000..42a5b84 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-down-on-square-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-down-on-square.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-down-on-square.svg new file mode 100644 index 0000000..da1b827 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-down-on-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-down-right.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-down-right.svg new file mode 100644 index 0000000..9cc7a30 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-down-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-down-tray.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-down-tray.svg new file mode 100644 index 0000000..a77546c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-down-tray.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-down.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-down.svg new file mode 100644 index 0000000..b5b04fd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-left-circle.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-left-circle.svg new file mode 100644 index 0000000..849cc5f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-left-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-left-on-rectangle.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-left-on-rectangle.svg new file mode 100644 index 0000000..0d6a3cc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-left-on-rectangle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-left.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-left.svg new file mode 100644 index 0000000..49f15d4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-long-down.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-long-down.svg new file mode 100644 index 0000000..eb7a92b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-long-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-long-left.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-long-left.svg new file mode 100644 index 0000000..d3e9005 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-long-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-long-right.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-long-right.svg new file mode 100644 index 0000000..413d6b5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-long-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-long-up.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-long-up.svg new file mode 100644 index 0000000..fb029f4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-long-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-path-rounded-square.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-path-rounded-square.svg new file mode 100644 index 0000000..0cfe39e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-path-rounded-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-path.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-path.svg new file mode 100644 index 0000000..7da4fd2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-path.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-right-circle.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-right-circle.svg new file mode 100644 index 0000000..e7bcb80 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-right-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-right-on-rectangle.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-right-on-rectangle.svg new file mode 100644 index 0000000..2b49bec --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-right-on-rectangle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-right.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-right.svg new file mode 100644 index 0000000..8527a52 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-small-down.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-small-down.svg new file mode 100644 index 0000000..1f1a210 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-small-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-small-left.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-small-left.svg new file mode 100644 index 0000000..778cb17 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-small-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-small-right.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-small-right.svg new file mode 100644 index 0000000..1b5fc64 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-small-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-small-up.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-small-up.svg new file mode 100644 index 0000000..4ed197e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-small-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-top-right-on-square.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-top-right-on-square.svg new file mode 100644 index 0000000..c4a9239 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-top-right-on-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-trending-down.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-trending-down.svg new file mode 100644 index 0000000..aebbb18 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-trending-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-trending-up.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-trending-up.svg new file mode 100644 index 0000000..868f3d3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-trending-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-up-circle.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-up-circle.svg new file mode 100644 index 0000000..51340d6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-up-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-up-left.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-up-left.svg new file mode 100644 index 0000000..ba4e54e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-up-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-up-on-square-stack.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-up-on-square-stack.svg new file mode 100644 index 0000000..0d4d823 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-up-on-square-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-up-on-square.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-up-on-square.svg new file mode 100644 index 0000000..2c38ea0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-up-on-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-up-right.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-up-right.svg new file mode 100644 index 0000000..0b7a372 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-up-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-up-tray.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-up-tray.svg new file mode 100644 index 0000000..448b853 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-up-tray.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-up.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-up.svg new file mode 100644 index 0000000..e269624 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-down.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-down.svg new file mode 100644 index 0000000..51f9929 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-left.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-left.svg new file mode 100644 index 0000000..b8f240e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-right.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-right.svg new file mode 100644 index 0000000..ece5091 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-up.svg b/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-up.svg new file mode 100644 index 0000000..b76c54b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrow-uturn-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrows-pointing-in.svg b/assets/vendor/heroicons/optimized/24/outline/arrows-pointing-in.svg new file mode 100644 index 0000000..0a8872d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrows-pointing-in.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrows-pointing-out.svg b/assets/vendor/heroicons/optimized/24/outline/arrows-pointing-out.svg new file mode 100644 index 0000000..936ac45 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrows-pointing-out.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrows-right-left.svg b/assets/vendor/heroicons/optimized/24/outline/arrows-right-left.svg new file mode 100644 index 0000000..18890f3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrows-right-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/arrows-up-down.svg b/assets/vendor/heroicons/optimized/24/outline/arrows-up-down.svg new file mode 100644 index 0000000..da4cdf3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/arrows-up-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/at-symbol.svg b/assets/vendor/heroicons/optimized/24/outline/at-symbol.svg new file mode 100644 index 0000000..fe2f644 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/at-symbol.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/backspace.svg b/assets/vendor/heroicons/optimized/24/outline/backspace.svg new file mode 100644 index 0000000..f76c5df --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/backspace.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/backward.svg b/assets/vendor/heroicons/optimized/24/outline/backward.svg new file mode 100644 index 0000000..fb1da49 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/backward.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/banknotes.svg b/assets/vendor/heroicons/optimized/24/outline/banknotes.svg new file mode 100644 index 0000000..0603b0d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/banknotes.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bars-2.svg b/assets/vendor/heroicons/optimized/24/outline/bars-2.svg new file mode 100644 index 0000000..9c49ca2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bars-2.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bars-3-bottom-left.svg b/assets/vendor/heroicons/optimized/24/outline/bars-3-bottom-left.svg new file mode 100644 index 0000000..e23bbc3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bars-3-bottom-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bars-3-bottom-right.svg b/assets/vendor/heroicons/optimized/24/outline/bars-3-bottom-right.svg new file mode 100644 index 0000000..a0f683d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bars-3-bottom-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bars-3-center-left.svg b/assets/vendor/heroicons/optimized/24/outline/bars-3-center-left.svg new file mode 100644 index 0000000..a8e83e1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bars-3-center-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bars-3.svg b/assets/vendor/heroicons/optimized/24/outline/bars-3.svg new file mode 100644 index 0000000..a7cf320 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bars-3.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bars-4.svg b/assets/vendor/heroicons/optimized/24/outline/bars-4.svg new file mode 100644 index 0000000..f34bddf --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bars-4.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bars-arrow-down.svg b/assets/vendor/heroicons/optimized/24/outline/bars-arrow-down.svg new file mode 100644 index 0000000..200fd3a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bars-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bars-arrow-up.svg b/assets/vendor/heroicons/optimized/24/outline/bars-arrow-up.svg new file mode 100644 index 0000000..d88bf4e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bars-arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/battery-0.svg b/assets/vendor/heroicons/optimized/24/outline/battery-0.svg new file mode 100644 index 0000000..fd2aa9d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/battery-0.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/battery-100.svg b/assets/vendor/heroicons/optimized/24/outline/battery-100.svg new file mode 100644 index 0000000..ba012c6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/battery-100.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/battery-50.svg b/assets/vendor/heroicons/optimized/24/outline/battery-50.svg new file mode 100644 index 0000000..f6f9838 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/battery-50.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/beaker.svg b/assets/vendor/heroicons/optimized/24/outline/beaker.svg new file mode 100644 index 0000000..2d143fd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/beaker.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bell-alert.svg b/assets/vendor/heroicons/optimized/24/outline/bell-alert.svg new file mode 100644 index 0000000..c4af427 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bell-alert.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bell-slash.svg b/assets/vendor/heroicons/optimized/24/outline/bell-slash.svg new file mode 100644 index 0000000..2df7520 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bell-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bell-snooze.svg b/assets/vendor/heroicons/optimized/24/outline/bell-snooze.svg new file mode 100644 index 0000000..117de29 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bell-snooze.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bell.svg b/assets/vendor/heroicons/optimized/24/outline/bell.svg new file mode 100644 index 0000000..63ab153 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bell.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bolt-slash.svg b/assets/vendor/heroicons/optimized/24/outline/bolt-slash.svg new file mode 100644 index 0000000..13af346 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bolt-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bolt.svg b/assets/vendor/heroicons/optimized/24/outline/bolt.svg new file mode 100644 index 0000000..5e629fe --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bolt.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/book-open.svg b/assets/vendor/heroicons/optimized/24/outline/book-open.svg new file mode 100644 index 0000000..a4153b6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/book-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bookmark-slash.svg b/assets/vendor/heroicons/optimized/24/outline/bookmark-slash.svg new file mode 100644 index 0000000..f3ae625 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bookmark-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bookmark-square.svg b/assets/vendor/heroicons/optimized/24/outline/bookmark-square.svg new file mode 100644 index 0000000..00e5cc3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bookmark-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bookmark.svg b/assets/vendor/heroicons/optimized/24/outline/bookmark.svg new file mode 100644 index 0000000..6d06e4f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bookmark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/briefcase.svg b/assets/vendor/heroicons/optimized/24/outline/briefcase.svg new file mode 100644 index 0000000..adab6ff --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/briefcase.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/bug-ant.svg b/assets/vendor/heroicons/optimized/24/outline/bug-ant.svg new file mode 100644 index 0000000..ac04fad --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/bug-ant.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/building-library.svg b/assets/vendor/heroicons/optimized/24/outline/building-library.svg new file mode 100644 index 0000000..4e2e1da --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/building-library.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/building-office-2.svg b/assets/vendor/heroicons/optimized/24/outline/building-office-2.svg new file mode 100644 index 0000000..45e063c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/building-office-2.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/building-office.svg b/assets/vendor/heroicons/optimized/24/outline/building-office.svg new file mode 100644 index 0000000..0efd982 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/building-office.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/building-storefront.svg b/assets/vendor/heroicons/optimized/24/outline/building-storefront.svg new file mode 100644 index 0000000..31fca55 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/building-storefront.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cake.svg b/assets/vendor/heroicons/optimized/24/outline/cake.svg new file mode 100644 index 0000000..a603e90 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cake.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/calculator.svg b/assets/vendor/heroicons/optimized/24/outline/calculator.svg new file mode 100644 index 0000000..d97740e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/calculator.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/calendar-days.svg b/assets/vendor/heroicons/optimized/24/outline/calendar-days.svg new file mode 100644 index 0000000..64b5f8f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/calendar-days.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/calendar.svg b/assets/vendor/heroicons/optimized/24/outline/calendar.svg new file mode 100644 index 0000000..5e44911 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/calendar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/camera.svg b/assets/vendor/heroicons/optimized/24/outline/camera.svg new file mode 100644 index 0000000..b8bdae3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/camera.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chart-bar-square.svg b/assets/vendor/heroicons/optimized/24/outline/chart-bar-square.svg new file mode 100644 index 0000000..d7fa42c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chart-bar-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chart-bar.svg b/assets/vendor/heroicons/optimized/24/outline/chart-bar.svg new file mode 100644 index 0000000..27f20fa --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chart-bar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chart-pie.svg b/assets/vendor/heroicons/optimized/24/outline/chart-pie.svg new file mode 100644 index 0000000..fa51c16 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chart-pie.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chat-bubble-bottom-center-text.svg b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-bottom-center-text.svg new file mode 100644 index 0000000..4bc306e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-bottom-center-text.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chat-bubble-bottom-center.svg b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-bottom-center.svg new file mode 100644 index 0000000..d59d02a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-bottom-center.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chat-bubble-left-ellipsis.svg b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-left-ellipsis.svg new file mode 100644 index 0000000..9a0ec73 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-left-ellipsis.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chat-bubble-left-right.svg b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-left-right.svg new file mode 100644 index 0000000..4d366b8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-left-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chat-bubble-left.svg b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-left.svg new file mode 100644 index 0000000..a41bf1e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chat-bubble-oval-left-ellipsis.svg b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-oval-left-ellipsis.svg new file mode 100644 index 0000000..83d1751 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-oval-left-ellipsis.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chat-bubble-oval-left.svg b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-oval-left.svg new file mode 100644 index 0000000..d0d0d89 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chat-bubble-oval-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/check-badge.svg b/assets/vendor/heroicons/optimized/24/outline/check-badge.svg new file mode 100644 index 0000000..8d6b79a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/check-badge.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/check-circle.svg b/assets/vendor/heroicons/optimized/24/outline/check-circle.svg new file mode 100644 index 0000000..d4471d6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/check-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/check.svg b/assets/vendor/heroicons/optimized/24/outline/check.svg new file mode 100644 index 0000000..7644e30 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/check.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chevron-double-down.svg b/assets/vendor/heroicons/optimized/24/outline/chevron-double-down.svg new file mode 100644 index 0000000..d7e9370 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chevron-double-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chevron-double-left.svg b/assets/vendor/heroicons/optimized/24/outline/chevron-double-left.svg new file mode 100644 index 0000000..95834af --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chevron-double-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chevron-double-right.svg b/assets/vendor/heroicons/optimized/24/outline/chevron-double-right.svg new file mode 100644 index 0000000..37a809d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chevron-double-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chevron-double-up.svg b/assets/vendor/heroicons/optimized/24/outline/chevron-double-up.svg new file mode 100644 index 0000000..5182691 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chevron-double-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chevron-down.svg b/assets/vendor/heroicons/optimized/24/outline/chevron-down.svg new file mode 100644 index 0000000..b38efa5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chevron-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chevron-left.svg b/assets/vendor/heroicons/optimized/24/outline/chevron-left.svg new file mode 100644 index 0000000..73fe99a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chevron-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chevron-right.svg b/assets/vendor/heroicons/optimized/24/outline/chevron-right.svg new file mode 100644 index 0000000..1e31bfd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chevron-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chevron-up-down.svg b/assets/vendor/heroicons/optimized/24/outline/chevron-up-down.svg new file mode 100644 index 0000000..27b1d4f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chevron-up-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/chevron-up.svg b/assets/vendor/heroicons/optimized/24/outline/chevron-up.svg new file mode 100644 index 0000000..713a6f1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/chevron-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/circle-stack.svg b/assets/vendor/heroicons/optimized/24/outline/circle-stack.svg new file mode 100644 index 0000000..b8fb769 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/circle-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/clipboard-document-check.svg b/assets/vendor/heroicons/optimized/24/outline/clipboard-document-check.svg new file mode 100644 index 0000000..7bb03a1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/clipboard-document-check.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/clipboard-document-list.svg b/assets/vendor/heroicons/optimized/24/outline/clipboard-document-list.svg new file mode 100644 index 0000000..4670777 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/clipboard-document-list.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/clipboard-document.svg b/assets/vendor/heroicons/optimized/24/outline/clipboard-document.svg new file mode 100644 index 0000000..783a333 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/clipboard-document.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/clipboard.svg b/assets/vendor/heroicons/optimized/24/outline/clipboard.svg new file mode 100644 index 0000000..ad9b943 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/clipboard.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/clock.svg b/assets/vendor/heroicons/optimized/24/outline/clock.svg new file mode 100644 index 0000000..337196c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/clock.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cloud-arrow-down.svg b/assets/vendor/heroicons/optimized/24/outline/cloud-arrow-down.svg new file mode 100644 index 0000000..7074791 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cloud-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cloud-arrow-up.svg b/assets/vendor/heroicons/optimized/24/outline/cloud-arrow-up.svg new file mode 100644 index 0000000..8b45081 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cloud-arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cloud.svg b/assets/vendor/heroicons/optimized/24/outline/cloud.svg new file mode 100644 index 0000000..55fd725 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cloud.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/code-bracket-square.svg b/assets/vendor/heroicons/optimized/24/outline/code-bracket-square.svg new file mode 100644 index 0000000..8308024 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/code-bracket-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/code-bracket.svg b/assets/vendor/heroicons/optimized/24/outline/code-bracket.svg new file mode 100644 index 0000000..3361add --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/code-bracket.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cog-6-tooth.svg b/assets/vendor/heroicons/optimized/24/outline/cog-6-tooth.svg new file mode 100644 index 0000000..d585645 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cog-6-tooth.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cog-8-tooth.svg b/assets/vendor/heroicons/optimized/24/outline/cog-8-tooth.svg new file mode 100644 index 0000000..28f85f4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cog-8-tooth.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cog.svg b/assets/vendor/heroicons/optimized/24/outline/cog.svg new file mode 100644 index 0000000..f2bad9f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cog.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/command-line.svg b/assets/vendor/heroicons/optimized/24/outline/command-line.svg new file mode 100644 index 0000000..baaf362 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/command-line.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/computer-desktop.svg b/assets/vendor/heroicons/optimized/24/outline/computer-desktop.svg new file mode 100644 index 0000000..fb9a6e0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/computer-desktop.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cpu-chip.svg b/assets/vendor/heroicons/optimized/24/outline/cpu-chip.svg new file mode 100644 index 0000000..cabc435 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cpu-chip.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/credit-card.svg b/assets/vendor/heroicons/optimized/24/outline/credit-card.svg new file mode 100644 index 0000000..3c0c917 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/credit-card.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cube-transparent.svg b/assets/vendor/heroicons/optimized/24/outline/cube-transparent.svg new file mode 100644 index 0000000..5a8adac --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cube-transparent.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cube.svg b/assets/vendor/heroicons/optimized/24/outline/cube.svg new file mode 100644 index 0000000..70b0091 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cube.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/currency-bangladeshi.svg b/assets/vendor/heroicons/optimized/24/outline/currency-bangladeshi.svg new file mode 100644 index 0000000..7f2fca3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/currency-bangladeshi.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/currency-dollar.svg b/assets/vendor/heroicons/optimized/24/outline/currency-dollar.svg new file mode 100644 index 0000000..d376f4c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/currency-dollar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/currency-euro.svg b/assets/vendor/heroicons/optimized/24/outline/currency-euro.svg new file mode 100644 index 0000000..8b9dd2e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/currency-euro.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/currency-pound.svg b/assets/vendor/heroicons/optimized/24/outline/currency-pound.svg new file mode 100644 index 0000000..8e7c52d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/currency-pound.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/currency-rupee.svg b/assets/vendor/heroicons/optimized/24/outline/currency-rupee.svg new file mode 100644 index 0000000..078bf05 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/currency-rupee.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/currency-yen.svg b/assets/vendor/heroicons/optimized/24/outline/currency-yen.svg new file mode 100644 index 0000000..254011a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/currency-yen.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cursor-arrow-rays.svg b/assets/vendor/heroicons/optimized/24/outline/cursor-arrow-rays.svg new file mode 100644 index 0000000..c29d0fd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cursor-arrow-rays.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/cursor-arrow-ripple.svg b/assets/vendor/heroicons/optimized/24/outline/cursor-arrow-ripple.svg new file mode 100644 index 0000000..500a04c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/cursor-arrow-ripple.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/device-phone-mobile.svg b/assets/vendor/heroicons/optimized/24/outline/device-phone-mobile.svg new file mode 100644 index 0000000..1caf911 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/device-phone-mobile.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/device-tablet.svg b/assets/vendor/heroicons/optimized/24/outline/device-tablet.svg new file mode 100644 index 0000000..7090ecb --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/device-tablet.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document-arrow-down.svg b/assets/vendor/heroicons/optimized/24/outline/document-arrow-down.svg new file mode 100644 index 0000000..04f6e65 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document-arrow-up.svg b/assets/vendor/heroicons/optimized/24/outline/document-arrow-up.svg new file mode 100644 index 0000000..c0ca80f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document-arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document-chart-bar.svg b/assets/vendor/heroicons/optimized/24/outline/document-chart-bar.svg new file mode 100644 index 0000000..2ffa3fe --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document-chart-bar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document-check.svg b/assets/vendor/heroicons/optimized/24/outline/document-check.svg new file mode 100644 index 0000000..5ea7d9c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document-check.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document-duplicate.svg b/assets/vendor/heroicons/optimized/24/outline/document-duplicate.svg new file mode 100644 index 0000000..acc6464 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document-duplicate.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document-magnifying-glass.svg b/assets/vendor/heroicons/optimized/24/outline/document-magnifying-glass.svg new file mode 100644 index 0000000..f94eff6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document-magnifying-glass.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document-minus.svg b/assets/vendor/heroicons/optimized/24/outline/document-minus.svg new file mode 100644 index 0000000..173cb1f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document-plus.svg b/assets/vendor/heroicons/optimized/24/outline/document-plus.svg new file mode 100644 index 0000000..9ec31ad --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document-text.svg b/assets/vendor/heroicons/optimized/24/outline/document-text.svg new file mode 100644 index 0000000..cd77136 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document-text.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/document.svg b/assets/vendor/heroicons/optimized/24/outline/document.svg new file mode 100644 index 0000000..863a8aa --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/document.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/ellipsis-horizontal-circle.svg b/assets/vendor/heroicons/optimized/24/outline/ellipsis-horizontal-circle.svg new file mode 100644 index 0000000..09aac53 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/ellipsis-horizontal-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/ellipsis-horizontal.svg b/assets/vendor/heroicons/optimized/24/outline/ellipsis-horizontal.svg new file mode 100644 index 0000000..7541be5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/ellipsis-horizontal.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/ellipsis-vertical.svg b/assets/vendor/heroicons/optimized/24/outline/ellipsis-vertical.svg new file mode 100644 index 0000000..4676cf3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/ellipsis-vertical.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/envelope-open.svg b/assets/vendor/heroicons/optimized/24/outline/envelope-open.svg new file mode 100644 index 0000000..ff9dccd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/envelope-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/envelope.svg b/assets/vendor/heroicons/optimized/24/outline/envelope.svg new file mode 100644 index 0000000..ae8ff72 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/envelope.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/exclamation-circle.svg b/assets/vendor/heroicons/optimized/24/outline/exclamation-circle.svg new file mode 100644 index 0000000..25ef36f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/exclamation-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/exclamation-triangle.svg b/assets/vendor/heroicons/optimized/24/outline/exclamation-triangle.svg new file mode 100644 index 0000000..c9742f1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/exclamation-triangle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/eye-dropper.svg b/assets/vendor/heroicons/optimized/24/outline/eye-dropper.svg new file mode 100644 index 0000000..c7263e1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/eye-dropper.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/eye-slash.svg b/assets/vendor/heroicons/optimized/24/outline/eye-slash.svg new file mode 100644 index 0000000..072c9f2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/eye-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/eye.svg b/assets/vendor/heroicons/optimized/24/outline/eye.svg new file mode 100644 index 0000000..2a54d63 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/eye.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/face-frown.svg b/assets/vendor/heroicons/optimized/24/outline/face-frown.svg new file mode 100644 index 0000000..ba0cab3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/face-frown.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/face-smile.svg b/assets/vendor/heroicons/optimized/24/outline/face-smile.svg new file mode 100644 index 0000000..5246524 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/face-smile.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/film.svg b/assets/vendor/heroicons/optimized/24/outline/film.svg new file mode 100644 index 0000000..d76e594 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/film.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/finger-print.svg b/assets/vendor/heroicons/optimized/24/outline/finger-print.svg new file mode 100644 index 0000000..0c1eeb2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/finger-print.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/fire.svg b/assets/vendor/heroicons/optimized/24/outline/fire.svg new file mode 100644 index 0000000..54c9748 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/fire.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/flag.svg b/assets/vendor/heroicons/optimized/24/outline/flag.svg new file mode 100644 index 0000000..dff4126 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/flag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/folder-arrow-down.svg b/assets/vendor/heroicons/optimized/24/outline/folder-arrow-down.svg new file mode 100644 index 0000000..96290cd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/folder-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/folder-minus.svg b/assets/vendor/heroicons/optimized/24/outline/folder-minus.svg new file mode 100644 index 0000000..824cb0e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/folder-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/folder-open.svg b/assets/vendor/heroicons/optimized/24/outline/folder-open.svg new file mode 100644 index 0000000..0721502 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/folder-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/folder-plus.svg b/assets/vendor/heroicons/optimized/24/outline/folder-plus.svg new file mode 100644 index 0000000..3df62d2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/folder-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/folder.svg b/assets/vendor/heroicons/optimized/24/outline/folder.svg new file mode 100644 index 0000000..3054819 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/folder.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/forward.svg b/assets/vendor/heroicons/optimized/24/outline/forward.svg new file mode 100644 index 0000000..cc80dc9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/forward.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/funnel.svg b/assets/vendor/heroicons/optimized/24/outline/funnel.svg new file mode 100644 index 0000000..338fa52 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/funnel.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/gif.svg b/assets/vendor/heroicons/optimized/24/outline/gif.svg new file mode 100644 index 0000000..ba8a186 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/gif.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/gift-top.svg b/assets/vendor/heroicons/optimized/24/outline/gift-top.svg new file mode 100644 index 0000000..055b6d2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/gift-top.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/gift.svg b/assets/vendor/heroicons/optimized/24/outline/gift.svg new file mode 100644 index 0000000..5445815 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/gift.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/globe-alt.svg b/assets/vendor/heroicons/optimized/24/outline/globe-alt.svg new file mode 100644 index 0000000..a605be0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/globe-alt.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/globe-americas.svg b/assets/vendor/heroicons/optimized/24/outline/globe-americas.svg new file mode 100644 index 0000000..5d1a5cb --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/globe-americas.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/globe-asia-australia.svg b/assets/vendor/heroicons/optimized/24/outline/globe-asia-australia.svg new file mode 100644 index 0000000..f4898fa --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/globe-asia-australia.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/globe-europe-africa.svg b/assets/vendor/heroicons/optimized/24/outline/globe-europe-africa.svg new file mode 100644 index 0000000..c8f797d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/globe-europe-africa.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/hand-raised.svg b/assets/vendor/heroicons/optimized/24/outline/hand-raised.svg new file mode 100644 index 0000000..859f1ab --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/hand-raised.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/hand-thumb-down.svg b/assets/vendor/heroicons/optimized/24/outline/hand-thumb-down.svg new file mode 100644 index 0000000..c588a53 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/hand-thumb-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/hand-thumb-up.svg b/assets/vendor/heroicons/optimized/24/outline/hand-thumb-up.svg new file mode 100644 index 0000000..66ca9c3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/hand-thumb-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/hashtag.svg b/assets/vendor/heroicons/optimized/24/outline/hashtag.svg new file mode 100644 index 0000000..3ae1060 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/hashtag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/heart.svg b/assets/vendor/heroicons/optimized/24/outline/heart.svg new file mode 100644 index 0000000..1084768 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/heart.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/home-modern.svg b/assets/vendor/heroicons/optimized/24/outline/home-modern.svg new file mode 100644 index 0000000..20f4e2c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/home-modern.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/home.svg b/assets/vendor/heroicons/optimized/24/outline/home.svg new file mode 100644 index 0000000..9543375 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/home.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/identification.svg b/assets/vendor/heroicons/optimized/24/outline/identification.svg new file mode 100644 index 0000000..bfd302a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/identification.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/inbox-arrow-down.svg b/assets/vendor/heroicons/optimized/24/outline/inbox-arrow-down.svg new file mode 100644 index 0000000..db6ebda --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/inbox-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/inbox-stack.svg b/assets/vendor/heroicons/optimized/24/outline/inbox-stack.svg new file mode 100644 index 0000000..6c1e55c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/inbox-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/inbox.svg b/assets/vendor/heroicons/optimized/24/outline/inbox.svg new file mode 100644 index 0000000..56b35cb --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/inbox.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/information-circle.svg b/assets/vendor/heroicons/optimized/24/outline/information-circle.svg new file mode 100644 index 0000000..c7fa9d7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/information-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/key.svg b/assets/vendor/heroicons/optimized/24/outline/key.svg new file mode 100644 index 0000000..e9684cd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/key.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/language.svg b/assets/vendor/heroicons/optimized/24/outline/language.svg new file mode 100644 index 0000000..0c606ef --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/language.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/lifebuoy.svg b/assets/vendor/heroicons/optimized/24/outline/lifebuoy.svg new file mode 100644 index 0000000..1660e99 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/lifebuoy.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/light-bulb.svg b/assets/vendor/heroicons/optimized/24/outline/light-bulb.svg new file mode 100644 index 0000000..e3f2d9a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/light-bulb.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/link.svg b/assets/vendor/heroicons/optimized/24/outline/link.svg new file mode 100644 index 0000000..916a703 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/link.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/list-bullet.svg b/assets/vendor/heroicons/optimized/24/outline/list-bullet.svg new file mode 100644 index 0000000..1474573 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/list-bullet.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/lock-closed.svg b/assets/vendor/heroicons/optimized/24/outline/lock-closed.svg new file mode 100644 index 0000000..08b23c9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/lock-closed.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/lock-open.svg b/assets/vendor/heroicons/optimized/24/outline/lock-open.svg new file mode 100644 index 0000000..c5595dd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/lock-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/magnifying-glass-circle.svg b/assets/vendor/heroicons/optimized/24/outline/magnifying-glass-circle.svg new file mode 100644 index 0000000..e71f8bf --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/magnifying-glass-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/magnifying-glass-minus.svg b/assets/vendor/heroicons/optimized/24/outline/magnifying-glass-minus.svg new file mode 100644 index 0000000..6bd11c1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/magnifying-glass-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/magnifying-glass-plus.svg b/assets/vendor/heroicons/optimized/24/outline/magnifying-glass-plus.svg new file mode 100644 index 0000000..5dab7de --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/magnifying-glass-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/magnifying-glass.svg b/assets/vendor/heroicons/optimized/24/outline/magnifying-glass.svg new file mode 100644 index 0000000..7cff88f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/magnifying-glass.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/map-pin.svg b/assets/vendor/heroicons/optimized/24/outline/map-pin.svg new file mode 100644 index 0000000..1f272f4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/map-pin.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/map.svg b/assets/vendor/heroicons/optimized/24/outline/map.svg new file mode 100644 index 0000000..f96c988 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/map.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/megaphone.svg b/assets/vendor/heroicons/optimized/24/outline/megaphone.svg new file mode 100644 index 0000000..ec19508 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/megaphone.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/microphone.svg b/assets/vendor/heroicons/optimized/24/outline/microphone.svg new file mode 100644 index 0000000..670b34f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/microphone.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/minus-circle.svg b/assets/vendor/heroicons/optimized/24/outline/minus-circle.svg new file mode 100644 index 0000000..b9630fc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/minus-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/minus-small.svg b/assets/vendor/heroicons/optimized/24/outline/minus-small.svg new file mode 100644 index 0000000..3e1a8b7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/minus-small.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/minus.svg b/assets/vendor/heroicons/optimized/24/outline/minus.svg new file mode 100644 index 0000000..781994c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/moon.svg b/assets/vendor/heroicons/optimized/24/outline/moon.svg new file mode 100644 index 0000000..91501fd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/moon.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/musical-note.svg b/assets/vendor/heroicons/optimized/24/outline/musical-note.svg new file mode 100644 index 0000000..c0667fc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/musical-note.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/newspaper.svg b/assets/vendor/heroicons/optimized/24/outline/newspaper.svg new file mode 100644 index 0000000..0a4ac57 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/newspaper.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/no-symbol.svg b/assets/vendor/heroicons/optimized/24/outline/no-symbol.svg new file mode 100644 index 0000000..19b0bd0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/no-symbol.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/paint-brush.svg b/assets/vendor/heroicons/optimized/24/outline/paint-brush.svg new file mode 100644 index 0000000..b66098f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/paint-brush.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/paper-airplane.svg b/assets/vendor/heroicons/optimized/24/outline/paper-airplane.svg new file mode 100644 index 0000000..32da43e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/paper-airplane.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/paper-clip.svg b/assets/vendor/heroicons/optimized/24/outline/paper-clip.svg new file mode 100644 index 0000000..1d78d81 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/paper-clip.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/pause-circle.svg b/assets/vendor/heroicons/optimized/24/outline/pause-circle.svg new file mode 100644 index 0000000..a9a9e93 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/pause-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/pause.svg b/assets/vendor/heroicons/optimized/24/outline/pause.svg new file mode 100644 index 0000000..9843f7b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/pause.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/pencil-square.svg b/assets/vendor/heroicons/optimized/24/outline/pencil-square.svg new file mode 100644 index 0000000..3de435b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/pencil-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/pencil.svg b/assets/vendor/heroicons/optimized/24/outline/pencil.svg new file mode 100644 index 0000000..0c8759a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/pencil.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/phone-arrow-down-left.svg b/assets/vendor/heroicons/optimized/24/outline/phone-arrow-down-left.svg new file mode 100644 index 0000000..b1b2e61 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/phone-arrow-down-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/phone-arrow-up-right.svg b/assets/vendor/heroicons/optimized/24/outline/phone-arrow-up-right.svg new file mode 100644 index 0000000..faaf659 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/phone-arrow-up-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/phone-x-mark.svg b/assets/vendor/heroicons/optimized/24/outline/phone-x-mark.svg new file mode 100644 index 0000000..0b8de6d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/phone-x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/phone.svg b/assets/vendor/heroicons/optimized/24/outline/phone.svg new file mode 100644 index 0000000..6f73149 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/phone.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/photo.svg b/assets/vendor/heroicons/optimized/24/outline/photo.svg new file mode 100644 index 0000000..6982a11 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/photo.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/play-circle.svg b/assets/vendor/heroicons/optimized/24/outline/play-circle.svg new file mode 100644 index 0000000..3a2fa63 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/play-circle.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/play-pause.svg b/assets/vendor/heroicons/optimized/24/outline/play-pause.svg new file mode 100644 index 0000000..4ce4e55 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/play-pause.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/play.svg b/assets/vendor/heroicons/optimized/24/outline/play.svg new file mode 100644 index 0000000..c0ae6de --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/play.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/plus-circle.svg b/assets/vendor/heroicons/optimized/24/outline/plus-circle.svg new file mode 100644 index 0000000..4da4d1f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/plus-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/plus-small.svg b/assets/vendor/heroicons/optimized/24/outline/plus-small.svg new file mode 100644 index 0000000..991ed59 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/plus-small.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/plus.svg b/assets/vendor/heroicons/optimized/24/outline/plus.svg new file mode 100644 index 0000000..0480817 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/power.svg b/assets/vendor/heroicons/optimized/24/outline/power.svg new file mode 100644 index 0000000..c4b2706 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/power.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/presentation-chart-bar.svg b/assets/vendor/heroicons/optimized/24/outline/presentation-chart-bar.svg new file mode 100644 index 0000000..87d8a6d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/presentation-chart-bar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/presentation-chart-line.svg b/assets/vendor/heroicons/optimized/24/outline/presentation-chart-line.svg new file mode 100644 index 0000000..2262e1f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/presentation-chart-line.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/printer.svg b/assets/vendor/heroicons/optimized/24/outline/printer.svg new file mode 100644 index 0000000..6f7c5fa --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/printer.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/puzzle-piece.svg b/assets/vendor/heroicons/optimized/24/outline/puzzle-piece.svg new file mode 100644 index 0000000..13aa1a5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/puzzle-piece.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/qr-code.svg b/assets/vendor/heroicons/optimized/24/outline/qr-code.svg new file mode 100644 index 0000000..662a4bd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/qr-code.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/question-mark-circle.svg b/assets/vendor/heroicons/optimized/24/outline/question-mark-circle.svg new file mode 100644 index 0000000..9fb2542 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/question-mark-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/queue-list.svg b/assets/vendor/heroicons/optimized/24/outline/queue-list.svg new file mode 100644 index 0000000..91c3f11 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/queue-list.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/radio.svg b/assets/vendor/heroicons/optimized/24/outline/radio.svg new file mode 100644 index 0000000..f9c177a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/radio.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/receipt-percent.svg b/assets/vendor/heroicons/optimized/24/outline/receipt-percent.svg new file mode 100644 index 0000000..2d19255 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/receipt-percent.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/receipt-refund.svg b/assets/vendor/heroicons/optimized/24/outline/receipt-refund.svg new file mode 100644 index 0000000..dc569fc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/receipt-refund.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/rectangle-group.svg b/assets/vendor/heroicons/optimized/24/outline/rectangle-group.svg new file mode 100644 index 0000000..b184964 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/rectangle-group.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/rectangle-stack.svg b/assets/vendor/heroicons/optimized/24/outline/rectangle-stack.svg new file mode 100644 index 0000000..e1c0272 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/rectangle-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/rocket-launch.svg b/assets/vendor/heroicons/optimized/24/outline/rocket-launch.svg new file mode 100644 index 0000000..6400ee8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/rocket-launch.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/rss.svg b/assets/vendor/heroicons/optimized/24/outline/rss.svg new file mode 100644 index 0000000..1c36b21 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/rss.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/scale.svg b/assets/vendor/heroicons/optimized/24/outline/scale.svg new file mode 100644 index 0000000..500e3c6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/scale.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/scissors.svg b/assets/vendor/heroicons/optimized/24/outline/scissors.svg new file mode 100644 index 0000000..a23dc81 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/scissors.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/server-stack.svg b/assets/vendor/heroicons/optimized/24/outline/server-stack.svg new file mode 100644 index 0000000..3b7fe32 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/server-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/server.svg b/assets/vendor/heroicons/optimized/24/outline/server.svg new file mode 100644 index 0000000..c1675f3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/server.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/share.svg b/assets/vendor/heroicons/optimized/24/outline/share.svg new file mode 100644 index 0000000..125b6d1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/share.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/shield-check.svg b/assets/vendor/heroicons/optimized/24/outline/shield-check.svg new file mode 100644 index 0000000..f9fa2b9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/shield-check.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/shield-exclamation.svg b/assets/vendor/heroicons/optimized/24/outline/shield-exclamation.svg new file mode 100644 index 0000000..b52a2ff --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/shield-exclamation.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/shopping-bag.svg b/assets/vendor/heroicons/optimized/24/outline/shopping-bag.svg new file mode 100644 index 0000000..f5a51bd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/shopping-bag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/shopping-cart.svg b/assets/vendor/heroicons/optimized/24/outline/shopping-cart.svg new file mode 100644 index 0000000..661477b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/shopping-cart.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/signal-slash.svg b/assets/vendor/heroicons/optimized/24/outline/signal-slash.svg new file mode 100644 index 0000000..62992c3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/signal-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/signal.svg b/assets/vendor/heroicons/optimized/24/outline/signal.svg new file mode 100644 index 0000000..56114d3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/signal.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/sparkles.svg b/assets/vendor/heroicons/optimized/24/outline/sparkles.svg new file mode 100644 index 0000000..5a78b09 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/sparkles.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/speaker-wave.svg b/assets/vendor/heroicons/optimized/24/outline/speaker-wave.svg new file mode 100644 index 0000000..1b6dde7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/speaker-wave.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/speaker-x-mark.svg b/assets/vendor/heroicons/optimized/24/outline/speaker-x-mark.svg new file mode 100644 index 0000000..427e21e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/speaker-x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/square-2-stack.svg b/assets/vendor/heroicons/optimized/24/outline/square-2-stack.svg new file mode 100644 index 0000000..bc5e253 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/square-2-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/square-3-stack-3d.svg b/assets/vendor/heroicons/optimized/24/outline/square-3-stack-3d.svg new file mode 100644 index 0000000..8af2704 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/square-3-stack-3d.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/squares-2x2.svg b/assets/vendor/heroicons/optimized/24/outline/squares-2x2.svg new file mode 100644 index 0000000..601366d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/squares-2x2.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/squares-plus.svg b/assets/vendor/heroicons/optimized/24/outline/squares-plus.svg new file mode 100644 index 0000000..b8033c1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/squares-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/star.svg b/assets/vendor/heroicons/optimized/24/outline/star.svg new file mode 100644 index 0000000..98aa481 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/star.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/stop-circle.svg b/assets/vendor/heroicons/optimized/24/outline/stop-circle.svg new file mode 100644 index 0000000..b570e8e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/stop-circle.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/stop.svg b/assets/vendor/heroicons/optimized/24/outline/stop.svg new file mode 100644 index 0000000..4ee917a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/stop.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/sun.svg b/assets/vendor/heroicons/optimized/24/outline/sun.svg new file mode 100644 index 0000000..5667cb3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/sun.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/swatch.svg b/assets/vendor/heroicons/optimized/24/outline/swatch.svg new file mode 100644 index 0000000..5b136eb --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/swatch.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/table-cells.svg b/assets/vendor/heroicons/optimized/24/outline/table-cells.svg new file mode 100644 index 0000000..cb37937 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/table-cells.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/tag.svg b/assets/vendor/heroicons/optimized/24/outline/tag.svg new file mode 100644 index 0000000..9620545 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/tag.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/ticket.svg b/assets/vendor/heroicons/optimized/24/outline/ticket.svg new file mode 100644 index 0000000..da2d69e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/ticket.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/trash.svg b/assets/vendor/heroicons/optimized/24/outline/trash.svg new file mode 100644 index 0000000..0d32d58 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/trash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/trophy.svg b/assets/vendor/heroicons/optimized/24/outline/trophy.svg new file mode 100644 index 0000000..f846e52 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/trophy.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/truck.svg b/assets/vendor/heroicons/optimized/24/outline/truck.svg new file mode 100644 index 0000000..6e1ea69 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/truck.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/tv.svg b/assets/vendor/heroicons/optimized/24/outline/tv.svg new file mode 100644 index 0000000..7b8a706 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/tv.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/user-circle.svg b/assets/vendor/heroicons/optimized/24/outline/user-circle.svg new file mode 100644 index 0000000..a177f26 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/user-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/user-group.svg b/assets/vendor/heroicons/optimized/24/outline/user-group.svg new file mode 100644 index 0000000..4e7089b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/user-group.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/user-minus.svg b/assets/vendor/heroicons/optimized/24/outline/user-minus.svg new file mode 100644 index 0000000..703478e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/user-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/user-plus.svg b/assets/vendor/heroicons/optimized/24/outline/user-plus.svg new file mode 100644 index 0000000..24533d1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/user-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/user.svg b/assets/vendor/heroicons/optimized/24/outline/user.svg new file mode 100644 index 0000000..e9b6c20 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/user.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/users.svg b/assets/vendor/heroicons/optimized/24/outline/users.svg new file mode 100644 index 0000000..87304a0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/users.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/variable.svg b/assets/vendor/heroicons/optimized/24/outline/variable.svg new file mode 100644 index 0000000..81fab04 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/variable.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/video-camera-slash.svg b/assets/vendor/heroicons/optimized/24/outline/video-camera-slash.svg new file mode 100644 index 0000000..d1de13e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/video-camera-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/video-camera.svg b/assets/vendor/heroicons/optimized/24/outline/video-camera.svg new file mode 100644 index 0000000..aae1a19 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/video-camera.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/view-columns.svg b/assets/vendor/heroicons/optimized/24/outline/view-columns.svg new file mode 100644 index 0000000..22a668e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/view-columns.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/viewfinder-circle.svg b/assets/vendor/heroicons/optimized/24/outline/viewfinder-circle.svg new file mode 100644 index 0000000..0583eef --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/viewfinder-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/wallet.svg b/assets/vendor/heroicons/optimized/24/outline/wallet.svg new file mode 100644 index 0000000..8f19d64 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/wallet.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/wifi.svg b/assets/vendor/heroicons/optimized/24/outline/wifi.svg new file mode 100644 index 0000000..084b3e1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/wifi.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/window.svg b/assets/vendor/heroicons/optimized/24/outline/window.svg new file mode 100644 index 0000000..4ffea71 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/window.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/wrench-screwdriver.svg b/assets/vendor/heroicons/optimized/24/outline/wrench-screwdriver.svg new file mode 100644 index 0000000..1023ae9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/wrench-screwdriver.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/wrench.svg b/assets/vendor/heroicons/optimized/24/outline/wrench.svg new file mode 100644 index 0000000..de3a6e2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/wrench.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/x-circle.svg b/assets/vendor/heroicons/optimized/24/outline/x-circle.svg new file mode 100644 index 0000000..294ba20 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/x-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/outline/x-mark.svg b/assets/vendor/heroicons/optimized/24/outline/x-mark.svg new file mode 100644 index 0000000..a6d9eb7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/outline/x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/academic-cap.svg b/assets/vendor/heroicons/optimized/24/solid/academic-cap.svg new file mode 100644 index 0000000..2a13ef2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/academic-cap.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/adjustments-horizontal.svg b/assets/vendor/heroicons/optimized/24/solid/adjustments-horizontal.svg new file mode 100644 index 0000000..ed9b950 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/adjustments-horizontal.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/adjustments-vertical.svg b/assets/vendor/heroicons/optimized/24/solid/adjustments-vertical.svg new file mode 100644 index 0000000..fc4b90c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/adjustments-vertical.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/archive-box-arrow-down.svg b/assets/vendor/heroicons/optimized/24/solid/archive-box-arrow-down.svg new file mode 100644 index 0000000..5999b72 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/archive-box-arrow-down.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/archive-box-x-mark.svg b/assets/vendor/heroicons/optimized/24/solid/archive-box-x-mark.svg new file mode 100644 index 0000000..e7dd0d6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/archive-box-x-mark.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/archive-box.svg b/assets/vendor/heroicons/optimized/24/solid/archive-box.svg new file mode 100644 index 0000000..74e0471 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/archive-box.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-down-circle.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-down-circle.svg new file mode 100644 index 0000000..c85a4ff --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-down-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-down-left.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-down-left.svg new file mode 100644 index 0000000..5cd0d4d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-down-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-down-on-square-stack.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-down-on-square-stack.svg new file mode 100644 index 0000000..d2ef0b3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-down-on-square-stack.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-down-on-square.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-down-on-square.svg new file mode 100644 index 0000000..35eab41 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-down-on-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-down-right.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-down-right.svg new file mode 100644 index 0000000..8c60942 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-down-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-down-tray.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-down-tray.svg new file mode 100644 index 0000000..a18c62d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-down-tray.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-down.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-down.svg new file mode 100644 index 0000000..5cb396b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-left-circle.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-left-circle.svg new file mode 100644 index 0000000..a937f8e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-left-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-left-on-rectangle.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-left-on-rectangle.svg new file mode 100644 index 0000000..972a85e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-left-on-rectangle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-left.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-left.svg new file mode 100644 index 0000000..51bef70 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-long-down.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-long-down.svg new file mode 100644 index 0000000..891774e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-long-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-long-left.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-long-left.svg new file mode 100644 index 0000000..aa12c0c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-long-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-long-right.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-long-right.svg new file mode 100644 index 0000000..0bcb6a0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-long-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-long-up.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-long-up.svg new file mode 100644 index 0000000..b36d8e0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-long-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-path-rounded-square.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-path-rounded-square.svg new file mode 100644 index 0000000..0808a57 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-path-rounded-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-path.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-path.svg new file mode 100644 index 0000000..48a71fd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-path.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-right-circle.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-right-circle.svg new file mode 100644 index 0000000..424f75a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-right-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-right-on-rectangle.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-right-on-rectangle.svg new file mode 100644 index 0000000..73a7a7e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-right-on-rectangle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-right.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-right.svg new file mode 100644 index 0000000..1b1bbd1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-small-down.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-small-down.svg new file mode 100644 index 0000000..790993f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-small-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-small-left.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-small-left.svg new file mode 100644 index 0000000..231b1b2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-small-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-small-right.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-small-right.svg new file mode 100644 index 0000000..5d91261 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-small-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-small-up.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-small-up.svg new file mode 100644 index 0000000..33b31cf --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-small-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-top-right-on-square.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-top-right-on-square.svg new file mode 100644 index 0000000..ac1bc37 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-top-right-on-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-trending-down.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-trending-down.svg new file mode 100644 index 0000000..f46b609 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-trending-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-trending-up.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-trending-up.svg new file mode 100644 index 0000000..f2ece6e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-trending-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-up-circle.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-up-circle.svg new file mode 100644 index 0000000..0a9999f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-up-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-up-left.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-up-left.svg new file mode 100644 index 0000000..b6f9c2e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-up-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-up-on-square-stack.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-up-on-square-stack.svg new file mode 100644 index 0000000..b661da7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-up-on-square-stack.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-up-on-square.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-up-on-square.svg new file mode 100644 index 0000000..cba893e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-up-on-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-up-right.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-up-right.svg new file mode 100644 index 0000000..7554631 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-up-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-up-tray.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-up-tray.svg new file mode 100644 index 0000000..19093f6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-up-tray.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-up.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-up.svg new file mode 100644 index 0000000..16f6c79 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-down.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-down.svg new file mode 100644 index 0000000..6975024 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-left.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-left.svg new file mode 100644 index 0000000..f0b679a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-right.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-right.svg new file mode 100644 index 0000000..2da0f98 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-up.svg b/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-up.svg new file mode 100644 index 0000000..8cfe23d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrow-uturn-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrows-pointing-in.svg b/assets/vendor/heroicons/optimized/24/solid/arrows-pointing-in.svg new file mode 100644 index 0000000..604920f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrows-pointing-in.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrows-pointing-out.svg b/assets/vendor/heroicons/optimized/24/solid/arrows-pointing-out.svg new file mode 100644 index 0000000..2399662 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrows-pointing-out.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrows-right-left.svg b/assets/vendor/heroicons/optimized/24/solid/arrows-right-left.svg new file mode 100644 index 0000000..93fb7f1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrows-right-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/arrows-up-down.svg b/assets/vendor/heroicons/optimized/24/solid/arrows-up-down.svg new file mode 100644 index 0000000..356e9ca --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/arrows-up-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/at-symbol.svg b/assets/vendor/heroicons/optimized/24/solid/at-symbol.svg new file mode 100644 index 0000000..a02c654 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/at-symbol.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/backspace.svg b/assets/vendor/heroicons/optimized/24/solid/backspace.svg new file mode 100644 index 0000000..e5a79c2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/backspace.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/backward.svg b/assets/vendor/heroicons/optimized/24/solid/backward.svg new file mode 100644 index 0000000..bdf2a01 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/backward.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/banknotes.svg b/assets/vendor/heroicons/optimized/24/solid/banknotes.svg new file mode 100644 index 0000000..1cc1803 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/banknotes.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bars-2.svg b/assets/vendor/heroicons/optimized/24/solid/bars-2.svg new file mode 100644 index 0000000..6ee47ab --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bars-2.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bars-3-bottom-left.svg b/assets/vendor/heroicons/optimized/24/solid/bars-3-bottom-left.svg new file mode 100644 index 0000000..a804c11 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bars-3-bottom-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bars-3-bottom-right.svg b/assets/vendor/heroicons/optimized/24/solid/bars-3-bottom-right.svg new file mode 100644 index 0000000..2fd11ad --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bars-3-bottom-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bars-3-center-left.svg b/assets/vendor/heroicons/optimized/24/solid/bars-3-center-left.svg new file mode 100644 index 0000000..9a2c170 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bars-3-center-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bars-3.svg b/assets/vendor/heroicons/optimized/24/solid/bars-3.svg new file mode 100644 index 0000000..85584e8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bars-3.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bars-4.svg b/assets/vendor/heroicons/optimized/24/solid/bars-4.svg new file mode 100644 index 0000000..e3591d1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bars-4.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bars-arrow-down.svg b/assets/vendor/heroicons/optimized/24/solid/bars-arrow-down.svg new file mode 100644 index 0000000..10140b8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bars-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bars-arrow-up.svg b/assets/vendor/heroicons/optimized/24/solid/bars-arrow-up.svg new file mode 100644 index 0000000..4b3d4a7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bars-arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/battery-0.svg b/assets/vendor/heroicons/optimized/24/solid/battery-0.svg new file mode 100644 index 0000000..f03a737 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/battery-0.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/battery-100.svg b/assets/vendor/heroicons/optimized/24/solid/battery-100.svg new file mode 100644 index 0000000..62e4ec9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/battery-100.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/battery-50.svg b/assets/vendor/heroicons/optimized/24/solid/battery-50.svg new file mode 100644 index 0000000..63344d7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/battery-50.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/beaker.svg b/assets/vendor/heroicons/optimized/24/solid/beaker.svg new file mode 100644 index 0000000..e0b73cc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/beaker.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bell-alert.svg b/assets/vendor/heroicons/optimized/24/solid/bell-alert.svg new file mode 100644 index 0000000..f280b00 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bell-alert.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bell-slash.svg b/assets/vendor/heroicons/optimized/24/solid/bell-slash.svg new file mode 100644 index 0000000..0ef076c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bell-slash.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bell-snooze.svg b/assets/vendor/heroicons/optimized/24/solid/bell-snooze.svg new file mode 100644 index 0000000..cf93ae0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bell-snooze.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bell.svg b/assets/vendor/heroicons/optimized/24/solid/bell.svg new file mode 100644 index 0000000..818496e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bell.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bolt-slash.svg b/assets/vendor/heroicons/optimized/24/solid/bolt-slash.svg new file mode 100644 index 0000000..59d24f7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bolt-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bolt.svg b/assets/vendor/heroicons/optimized/24/solid/bolt.svg new file mode 100644 index 0000000..596c47a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bolt.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/book-open.svg b/assets/vendor/heroicons/optimized/24/solid/book-open.svg new file mode 100644 index 0000000..2e0a181 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/book-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bookmark-slash.svg b/assets/vendor/heroicons/optimized/24/solid/bookmark-slash.svg new file mode 100644 index 0000000..8435a02 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bookmark-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bookmark-square.svg b/assets/vendor/heroicons/optimized/24/solid/bookmark-square.svg new file mode 100644 index 0000000..a4d3ca5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bookmark-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bookmark.svg b/assets/vendor/heroicons/optimized/24/solid/bookmark.svg new file mode 100644 index 0000000..e9f3fb7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bookmark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/briefcase.svg b/assets/vendor/heroicons/optimized/24/solid/briefcase.svg new file mode 100644 index 0000000..a66af56 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/briefcase.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/bug-ant.svg b/assets/vendor/heroicons/optimized/24/solid/bug-ant.svg new file mode 100644 index 0000000..3c16cbc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/bug-ant.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/building-library.svg b/assets/vendor/heroicons/optimized/24/solid/building-library.svg new file mode 100644 index 0000000..90f8640 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/building-library.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/building-office-2.svg b/assets/vendor/heroicons/optimized/24/solid/building-office-2.svg new file mode 100644 index 0000000..240eedb --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/building-office-2.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/building-office.svg b/assets/vendor/heroicons/optimized/24/solid/building-office.svg new file mode 100644 index 0000000..9883e33 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/building-office.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/building-storefront.svg b/assets/vendor/heroicons/optimized/24/solid/building-storefront.svg new file mode 100644 index 0000000..f3b54ba --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/building-storefront.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cake.svg b/assets/vendor/heroicons/optimized/24/solid/cake.svg new file mode 100644 index 0000000..f13b308 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cake.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/calculator.svg b/assets/vendor/heroicons/optimized/24/solid/calculator.svg new file mode 100644 index 0000000..e058510 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/calculator.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/calendar-days.svg b/assets/vendor/heroicons/optimized/24/solid/calendar-days.svg new file mode 100644 index 0000000..0df9b16 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/calendar-days.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/calendar.svg b/assets/vendor/heroicons/optimized/24/solid/calendar.svg new file mode 100644 index 0000000..27e208c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/calendar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/camera.svg b/assets/vendor/heroicons/optimized/24/solid/camera.svg new file mode 100644 index 0000000..e58165d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/camera.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chart-bar-square.svg b/assets/vendor/heroicons/optimized/24/solid/chart-bar-square.svg new file mode 100644 index 0000000..d4af840 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chart-bar-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chart-bar.svg b/assets/vendor/heroicons/optimized/24/solid/chart-bar.svg new file mode 100644 index 0000000..abe5349 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chart-bar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chart-pie.svg b/assets/vendor/heroicons/optimized/24/solid/chart-pie.svg new file mode 100644 index 0000000..5aea729 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chart-pie.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chat-bubble-bottom-center-text.svg b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-bottom-center-text.svg new file mode 100644 index 0000000..bff24c4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-bottom-center-text.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chat-bubble-bottom-center.svg b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-bottom-center.svg new file mode 100644 index 0000000..eab7a41 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-bottom-center.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chat-bubble-left-ellipsis.svg b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-left-ellipsis.svg new file mode 100644 index 0000000..1ee6159 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-left-ellipsis.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chat-bubble-left-right.svg b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-left-right.svg new file mode 100644 index 0000000..80ad26d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-left-right.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chat-bubble-left.svg b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-left.svg new file mode 100644 index 0000000..3dd81b7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chat-bubble-oval-left-ellipsis.svg b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-oval-left-ellipsis.svg new file mode 100644 index 0000000..815c6d7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-oval-left-ellipsis.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chat-bubble-oval-left.svg b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-oval-left.svg new file mode 100644 index 0000000..473b921 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chat-bubble-oval-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/check-badge.svg b/assets/vendor/heroicons/optimized/24/solid/check-badge.svg new file mode 100644 index 0000000..058b329 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/check-badge.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/check-circle.svg b/assets/vendor/heroicons/optimized/24/solid/check-circle.svg new file mode 100644 index 0000000..2b90831 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/check-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/check.svg b/assets/vendor/heroicons/optimized/24/solid/check.svg new file mode 100644 index 0000000..2a6bc17 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/check.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chevron-double-down.svg b/assets/vendor/heroicons/optimized/24/solid/chevron-double-down.svg new file mode 100644 index 0000000..ddbe304 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chevron-double-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chevron-double-left.svg b/assets/vendor/heroicons/optimized/24/solid/chevron-double-left.svg new file mode 100644 index 0000000..5c6539d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chevron-double-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chevron-double-right.svg b/assets/vendor/heroicons/optimized/24/solid/chevron-double-right.svg new file mode 100644 index 0000000..7e25238 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chevron-double-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chevron-double-up.svg b/assets/vendor/heroicons/optimized/24/solid/chevron-double-up.svg new file mode 100644 index 0000000..029e687 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chevron-double-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chevron-down.svg b/assets/vendor/heroicons/optimized/24/solid/chevron-down.svg new file mode 100644 index 0000000..4f9ce7e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chevron-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chevron-left.svg b/assets/vendor/heroicons/optimized/24/solid/chevron-left.svg new file mode 100644 index 0000000..2d89e8b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chevron-left.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chevron-right.svg b/assets/vendor/heroicons/optimized/24/solid/chevron-right.svg new file mode 100644 index 0000000..36e4859 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chevron-right.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chevron-up-down.svg b/assets/vendor/heroicons/optimized/24/solid/chevron-up-down.svg new file mode 100644 index 0000000..58edbc3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chevron-up-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/chevron-up.svg b/assets/vendor/heroicons/optimized/24/solid/chevron-up.svg new file mode 100644 index 0000000..9abe9cd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/chevron-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/circle-stack.svg b/assets/vendor/heroicons/optimized/24/solid/circle-stack.svg new file mode 100644 index 0000000..5a49d80 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/circle-stack.svg @@ -0,0 +1,6 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/clipboard-document-check.svg b/assets/vendor/heroicons/optimized/24/solid/clipboard-document-check.svg new file mode 100644 index 0000000..21ec021 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/clipboard-document-check.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/clipboard-document-list.svg b/assets/vendor/heroicons/optimized/24/solid/clipboard-document-list.svg new file mode 100644 index 0000000..d60bed5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/clipboard-document-list.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/clipboard-document.svg b/assets/vendor/heroicons/optimized/24/solid/clipboard-document.svg new file mode 100644 index 0000000..d70b708 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/clipboard-document.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/clipboard.svg b/assets/vendor/heroicons/optimized/24/solid/clipboard.svg new file mode 100644 index 0000000..c09970f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/clipboard.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/clock.svg b/assets/vendor/heroicons/optimized/24/solid/clock.svg new file mode 100644 index 0000000..1d6fb4a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/clock.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cloud-arrow-down.svg b/assets/vendor/heroicons/optimized/24/solid/cloud-arrow-down.svg new file mode 100644 index 0000000..d6cf7c5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cloud-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cloud-arrow-up.svg b/assets/vendor/heroicons/optimized/24/solid/cloud-arrow-up.svg new file mode 100644 index 0000000..7e0dcee --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cloud-arrow-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cloud.svg b/assets/vendor/heroicons/optimized/24/solid/cloud.svg new file mode 100644 index 0000000..95d0c73 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cloud.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/code-bracket-square.svg b/assets/vendor/heroicons/optimized/24/solid/code-bracket-square.svg new file mode 100644 index 0000000..103f73b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/code-bracket-square.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/code-bracket.svg b/assets/vendor/heroicons/optimized/24/solid/code-bracket.svg new file mode 100644 index 0000000..9f331ef --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/code-bracket.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cog-6-tooth.svg b/assets/vendor/heroicons/optimized/24/solid/cog-6-tooth.svg new file mode 100644 index 0000000..ba6fca5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cog-6-tooth.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cog-8-tooth.svg b/assets/vendor/heroicons/optimized/24/solid/cog-8-tooth.svg new file mode 100644 index 0000000..9b9b1a6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cog-8-tooth.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cog.svg b/assets/vendor/heroicons/optimized/24/solid/cog.svg new file mode 100644 index 0000000..e854f73 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cog.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/command-line.svg b/assets/vendor/heroicons/optimized/24/solid/command-line.svg new file mode 100644 index 0000000..e2a0af8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/command-line.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/computer-desktop.svg b/assets/vendor/heroicons/optimized/24/solid/computer-desktop.svg new file mode 100644 index 0000000..5b7f2ef --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/computer-desktop.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cpu-chip.svg b/assets/vendor/heroicons/optimized/24/solid/cpu-chip.svg new file mode 100644 index 0000000..e20f6fb --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cpu-chip.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/credit-card.svg b/assets/vendor/heroicons/optimized/24/solid/credit-card.svg new file mode 100644 index 0000000..fe4dc14 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/credit-card.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cube-transparent.svg b/assets/vendor/heroicons/optimized/24/solid/cube-transparent.svg new file mode 100644 index 0000000..5577f26 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cube-transparent.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cube.svg b/assets/vendor/heroicons/optimized/24/solid/cube.svg new file mode 100644 index 0000000..b0029f2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cube.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/currency-bangladeshi.svg b/assets/vendor/heroicons/optimized/24/solid/currency-bangladeshi.svg new file mode 100644 index 0000000..ca13c68 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/currency-bangladeshi.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/currency-dollar.svg b/assets/vendor/heroicons/optimized/24/solid/currency-dollar.svg new file mode 100644 index 0000000..e0155df --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/currency-dollar.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/currency-euro.svg b/assets/vendor/heroicons/optimized/24/solid/currency-euro.svg new file mode 100644 index 0000000..2926c25 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/currency-euro.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/currency-pound.svg b/assets/vendor/heroicons/optimized/24/solid/currency-pound.svg new file mode 100644 index 0000000..547f725 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/currency-pound.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/currency-rupee.svg b/assets/vendor/heroicons/optimized/24/solid/currency-rupee.svg new file mode 100644 index 0000000..2ee9b46 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/currency-rupee.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/currency-yen.svg b/assets/vendor/heroicons/optimized/24/solid/currency-yen.svg new file mode 100644 index 0000000..65ef9bc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/currency-yen.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cursor-arrow-rays.svg b/assets/vendor/heroicons/optimized/24/solid/cursor-arrow-rays.svg new file mode 100644 index 0000000..c0e462b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cursor-arrow-rays.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/cursor-arrow-ripple.svg b/assets/vendor/heroicons/optimized/24/solid/cursor-arrow-ripple.svg new file mode 100644 index 0000000..867faa4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/cursor-arrow-ripple.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/device-phone-mobile.svg b/assets/vendor/heroicons/optimized/24/solid/device-phone-mobile.svg new file mode 100644 index 0000000..eec0738 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/device-phone-mobile.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/device-tablet.svg b/assets/vendor/heroicons/optimized/24/solid/device-tablet.svg new file mode 100644 index 0000000..88e2cc8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/device-tablet.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document-arrow-down.svg b/assets/vendor/heroicons/optimized/24/solid/document-arrow-down.svg new file mode 100644 index 0000000..77ac19c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document-arrow-down.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document-arrow-up.svg b/assets/vendor/heroicons/optimized/24/solid/document-arrow-up.svg new file mode 100644 index 0000000..bc26cb9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document-arrow-up.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document-chart-bar.svg b/assets/vendor/heroicons/optimized/24/solid/document-chart-bar.svg new file mode 100644 index 0000000..83d0eac --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document-chart-bar.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document-check.svg b/assets/vendor/heroicons/optimized/24/solid/document-check.svg new file mode 100644 index 0000000..e827854 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document-check.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document-duplicate.svg b/assets/vendor/heroicons/optimized/24/solid/document-duplicate.svg new file mode 100644 index 0000000..fa7375d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document-duplicate.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document-magnifying-glass.svg b/assets/vendor/heroicons/optimized/24/solid/document-magnifying-glass.svg new file mode 100644 index 0000000..ab165f7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document-magnifying-glass.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document-minus.svg b/assets/vendor/heroicons/optimized/24/solid/document-minus.svg new file mode 100644 index 0000000..265c6eb --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document-minus.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document-plus.svg b/assets/vendor/heroicons/optimized/24/solid/document-plus.svg new file mode 100644 index 0000000..5e31459 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document-plus.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document-text.svg b/assets/vendor/heroicons/optimized/24/solid/document-text.svg new file mode 100644 index 0000000..73b30cd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document-text.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/document.svg b/assets/vendor/heroicons/optimized/24/solid/document.svg new file mode 100644 index 0000000..a05f20f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/document.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/ellipsis-horizontal-circle.svg b/assets/vendor/heroicons/optimized/24/solid/ellipsis-horizontal-circle.svg new file mode 100644 index 0000000..6ec2b50 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/ellipsis-horizontal-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/ellipsis-horizontal.svg b/assets/vendor/heroicons/optimized/24/solid/ellipsis-horizontal.svg new file mode 100644 index 0000000..ddb5a3f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/ellipsis-horizontal.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/ellipsis-vertical.svg b/assets/vendor/heroicons/optimized/24/solid/ellipsis-vertical.svg new file mode 100644 index 0000000..792c0ae --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/ellipsis-vertical.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/envelope-open.svg b/assets/vendor/heroicons/optimized/24/solid/envelope-open.svg new file mode 100644 index 0000000..e6bf97b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/envelope-open.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/envelope.svg b/assets/vendor/heroicons/optimized/24/solid/envelope.svg new file mode 100644 index 0000000..702341b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/envelope.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/exclamation-circle.svg b/assets/vendor/heroicons/optimized/24/solid/exclamation-circle.svg new file mode 100644 index 0000000..fdaadc0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/exclamation-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/exclamation-triangle.svg b/assets/vendor/heroicons/optimized/24/solid/exclamation-triangle.svg new file mode 100644 index 0000000..627a712 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/exclamation-triangle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/eye-dropper.svg b/assets/vendor/heroicons/optimized/24/solid/eye-dropper.svg new file mode 100644 index 0000000..15b16d0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/eye-dropper.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/eye-slash.svg b/assets/vendor/heroicons/optimized/24/solid/eye-slash.svg new file mode 100644 index 0000000..11ef99b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/eye-slash.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/eye.svg b/assets/vendor/heroicons/optimized/24/solid/eye.svg new file mode 100644 index 0000000..a648db3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/eye.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/face-frown.svg b/assets/vendor/heroicons/optimized/24/solid/face-frown.svg new file mode 100644 index 0000000..7040d58 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/face-frown.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/face-smile.svg b/assets/vendor/heroicons/optimized/24/solid/face-smile.svg new file mode 100644 index 0000000..d5e75a2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/face-smile.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/film.svg b/assets/vendor/heroicons/optimized/24/solid/film.svg new file mode 100644 index 0000000..fbd26cc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/film.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/finger-print.svg b/assets/vendor/heroicons/optimized/24/solid/finger-print.svg new file mode 100644 index 0000000..68e72b4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/finger-print.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/fire.svg b/assets/vendor/heroicons/optimized/24/solid/fire.svg new file mode 100644 index 0000000..93b1b1f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/fire.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/flag.svg b/assets/vendor/heroicons/optimized/24/solid/flag.svg new file mode 100644 index 0000000..8c67b01 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/flag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/folder-arrow-down.svg b/assets/vendor/heroicons/optimized/24/solid/folder-arrow-down.svg new file mode 100644 index 0000000..5d963b4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/folder-arrow-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/folder-minus.svg b/assets/vendor/heroicons/optimized/24/solid/folder-minus.svg new file mode 100644 index 0000000..d0292b8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/folder-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/folder-open.svg b/assets/vendor/heroicons/optimized/24/solid/folder-open.svg new file mode 100644 index 0000000..b9d80ba --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/folder-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/folder-plus.svg b/assets/vendor/heroicons/optimized/24/solid/folder-plus.svg new file mode 100644 index 0000000..efaf949 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/folder-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/folder.svg b/assets/vendor/heroicons/optimized/24/solid/folder.svg new file mode 100644 index 0000000..a7847f8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/folder.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/forward.svg b/assets/vendor/heroicons/optimized/24/solid/forward.svg new file mode 100644 index 0000000..de90863 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/forward.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/funnel.svg b/assets/vendor/heroicons/optimized/24/solid/funnel.svg new file mode 100644 index 0000000..fe5699f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/funnel.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/gif.svg b/assets/vendor/heroicons/optimized/24/solid/gif.svg new file mode 100644 index 0000000..283e6e5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/gif.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/gift-top.svg b/assets/vendor/heroicons/optimized/24/solid/gift-top.svg new file mode 100644 index 0000000..4bd4e6f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/gift-top.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/gift.svg b/assets/vendor/heroicons/optimized/24/solid/gift.svg new file mode 100644 index 0000000..2ca6c92 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/gift.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/globe-alt.svg b/assets/vendor/heroicons/optimized/24/solid/globe-alt.svg new file mode 100644 index 0000000..0cbacae --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/globe-alt.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/globe-americas.svg b/assets/vendor/heroicons/optimized/24/solid/globe-americas.svg new file mode 100644 index 0000000..5ae0d5d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/globe-americas.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/globe-asia-australia.svg b/assets/vendor/heroicons/optimized/24/solid/globe-asia-australia.svg new file mode 100644 index 0000000..14249d6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/globe-asia-australia.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/globe-europe-africa.svg b/assets/vendor/heroicons/optimized/24/solid/globe-europe-africa.svg new file mode 100644 index 0000000..49a78fd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/globe-europe-africa.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/hand-raised.svg b/assets/vendor/heroicons/optimized/24/solid/hand-raised.svg new file mode 100644 index 0000000..1717d51 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/hand-raised.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/hand-thumb-down.svg b/assets/vendor/heroicons/optimized/24/solid/hand-thumb-down.svg new file mode 100644 index 0000000..7a2b9a6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/hand-thumb-down.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/hand-thumb-up.svg b/assets/vendor/heroicons/optimized/24/solid/hand-thumb-up.svg new file mode 100644 index 0000000..4942d2d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/hand-thumb-up.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/hashtag.svg b/assets/vendor/heroicons/optimized/24/solid/hashtag.svg new file mode 100644 index 0000000..29e677d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/hashtag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/heart.svg b/assets/vendor/heroicons/optimized/24/solid/heart.svg new file mode 100644 index 0000000..b5f0d95 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/heart.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/home-modern.svg b/assets/vendor/heroicons/optimized/24/solid/home-modern.svg new file mode 100644 index 0000000..488685a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/home-modern.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/home.svg b/assets/vendor/heroicons/optimized/24/solid/home.svg new file mode 100644 index 0000000..ec0bae1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/home.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/identification.svg b/assets/vendor/heroicons/optimized/24/solid/identification.svg new file mode 100644 index 0000000..829b24d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/identification.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/inbox-arrow-down.svg b/assets/vendor/heroicons/optimized/24/solid/inbox-arrow-down.svg new file mode 100644 index 0000000..4fd220b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/inbox-arrow-down.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/inbox-stack.svg b/assets/vendor/heroicons/optimized/24/solid/inbox-stack.svg new file mode 100644 index 0000000..fffab99 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/inbox-stack.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/inbox.svg b/assets/vendor/heroicons/optimized/24/solid/inbox.svg new file mode 100644 index 0000000..b74380e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/inbox.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/information-circle.svg b/assets/vendor/heroicons/optimized/24/solid/information-circle.svg new file mode 100644 index 0000000..bd2723b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/information-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/key.svg b/assets/vendor/heroicons/optimized/24/solid/key.svg new file mode 100644 index 0000000..6acee27 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/key.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/language.svg b/assets/vendor/heroicons/optimized/24/solid/language.svg new file mode 100644 index 0000000..60d1aee --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/language.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/lifebuoy.svg b/assets/vendor/heroicons/optimized/24/solid/lifebuoy.svg new file mode 100644 index 0000000..9ddc8d4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/lifebuoy.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/light-bulb.svg b/assets/vendor/heroicons/optimized/24/solid/light-bulb.svg new file mode 100644 index 0000000..ff49cb3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/light-bulb.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/link.svg b/assets/vendor/heroicons/optimized/24/solid/link.svg new file mode 100644 index 0000000..a6dc093 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/link.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/list-bullet.svg b/assets/vendor/heroicons/optimized/24/solid/list-bullet.svg new file mode 100644 index 0000000..7983877 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/list-bullet.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/lock-closed.svg b/assets/vendor/heroicons/optimized/24/solid/lock-closed.svg new file mode 100644 index 0000000..8a5a6d7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/lock-closed.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/lock-open.svg b/assets/vendor/heroicons/optimized/24/solid/lock-open.svg new file mode 100644 index 0000000..4562f7e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/lock-open.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/magnifying-glass-circle.svg b/assets/vendor/heroicons/optimized/24/solid/magnifying-glass-circle.svg new file mode 100644 index 0000000..17a6f28 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/magnifying-glass-circle.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/magnifying-glass-minus.svg b/assets/vendor/heroicons/optimized/24/solid/magnifying-glass-minus.svg new file mode 100644 index 0000000..06ae161 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/magnifying-glass-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/magnifying-glass-plus.svg b/assets/vendor/heroicons/optimized/24/solid/magnifying-glass-plus.svg new file mode 100644 index 0000000..92040d0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/magnifying-glass-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/magnifying-glass.svg b/assets/vendor/heroicons/optimized/24/solid/magnifying-glass.svg new file mode 100644 index 0000000..b602e08 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/magnifying-glass.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/map-pin.svg b/assets/vendor/heroicons/optimized/24/solid/map-pin.svg new file mode 100644 index 0000000..92e6785 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/map-pin.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/map.svg b/assets/vendor/heroicons/optimized/24/solid/map.svg new file mode 100644 index 0000000..9d4f7dd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/map.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/megaphone.svg b/assets/vendor/heroicons/optimized/24/solid/megaphone.svg new file mode 100644 index 0000000..b1f1e10 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/megaphone.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/microphone.svg b/assets/vendor/heroicons/optimized/24/solid/microphone.svg new file mode 100644 index 0000000..a5f4cb6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/microphone.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/minus-circle.svg b/assets/vendor/heroicons/optimized/24/solid/minus-circle.svg new file mode 100644 index 0000000..8c981ab --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/minus-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/minus-small.svg b/assets/vendor/heroicons/optimized/24/solid/minus-small.svg new file mode 100644 index 0000000..782213e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/minus-small.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/minus.svg b/assets/vendor/heroicons/optimized/24/solid/minus.svg new file mode 100644 index 0000000..1fa7117 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/moon.svg b/assets/vendor/heroicons/optimized/24/solid/moon.svg new file mode 100644 index 0000000..97d5c5a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/moon.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/musical-note.svg b/assets/vendor/heroicons/optimized/24/solid/musical-note.svg new file mode 100644 index 0000000..a9ab9b5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/musical-note.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/newspaper.svg b/assets/vendor/heroicons/optimized/24/solid/newspaper.svg new file mode 100644 index 0000000..d1f2c1e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/newspaper.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/no-symbol.svg b/assets/vendor/heroicons/optimized/24/solid/no-symbol.svg new file mode 100644 index 0000000..42eb771 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/no-symbol.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/paint-brush.svg b/assets/vendor/heroicons/optimized/24/solid/paint-brush.svg new file mode 100644 index 0000000..35fd5a6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/paint-brush.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/paper-airplane.svg b/assets/vendor/heroicons/optimized/24/solid/paper-airplane.svg new file mode 100644 index 0000000..9365a57 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/paper-airplane.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/paper-clip.svg b/assets/vendor/heroicons/optimized/24/solid/paper-clip.svg new file mode 100644 index 0000000..0a0dcdd --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/paper-clip.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/pause-circle.svg b/assets/vendor/heroicons/optimized/24/solid/pause-circle.svg new file mode 100644 index 0000000..4fe4f2b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/pause-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/pause.svg b/assets/vendor/heroicons/optimized/24/solid/pause.svg new file mode 100644 index 0000000..2e121ac --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/pause.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/pencil-square.svg b/assets/vendor/heroicons/optimized/24/solid/pencil-square.svg new file mode 100644 index 0000000..5f4aaf8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/pencil-square.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/pencil.svg b/assets/vendor/heroicons/optimized/24/solid/pencil.svg new file mode 100644 index 0000000..78ec61a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/pencil.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/phone-arrow-down-left.svg b/assets/vendor/heroicons/optimized/24/solid/phone-arrow-down-left.svg new file mode 100644 index 0000000..06f3ba0 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/phone-arrow-down-left.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/phone-arrow-up-right.svg b/assets/vendor/heroicons/optimized/24/solid/phone-arrow-up-right.svg new file mode 100644 index 0000000..678c4f6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/phone-arrow-up-right.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/phone-x-mark.svg b/assets/vendor/heroicons/optimized/24/solid/phone-x-mark.svg new file mode 100644 index 0000000..a017a7d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/phone-x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/phone.svg b/assets/vendor/heroicons/optimized/24/solid/phone.svg new file mode 100644 index 0000000..ca2a6bc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/phone.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/photo.svg b/assets/vendor/heroicons/optimized/24/solid/photo.svg new file mode 100644 index 0000000..57e023d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/photo.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/play-circle.svg b/assets/vendor/heroicons/optimized/24/solid/play-circle.svg new file mode 100644 index 0000000..752273a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/play-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/play-pause.svg b/assets/vendor/heroicons/optimized/24/solid/play-pause.svg new file mode 100644 index 0000000..e5bb17c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/play-pause.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/play.svg b/assets/vendor/heroicons/optimized/24/solid/play.svg new file mode 100644 index 0000000..da0e4e2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/play.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/plus-circle.svg b/assets/vendor/heroicons/optimized/24/solid/plus-circle.svg new file mode 100644 index 0000000..b45f965 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/plus-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/plus-small.svg b/assets/vendor/heroicons/optimized/24/solid/plus-small.svg new file mode 100644 index 0000000..0c4b744 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/plus-small.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/plus.svg b/assets/vendor/heroicons/optimized/24/solid/plus.svg new file mode 100644 index 0000000..85d3b14 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/power.svg b/assets/vendor/heroicons/optimized/24/solid/power.svg new file mode 100644 index 0000000..2bf830f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/power.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/presentation-chart-bar.svg b/assets/vendor/heroicons/optimized/24/solid/presentation-chart-bar.svg new file mode 100644 index 0000000..cb0bc9a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/presentation-chart-bar.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/presentation-chart-line.svg b/assets/vendor/heroicons/optimized/24/solid/presentation-chart-line.svg new file mode 100644 index 0000000..54e10b2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/presentation-chart-line.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/printer.svg b/assets/vendor/heroicons/optimized/24/solid/printer.svg new file mode 100644 index 0000000..4fce791 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/printer.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/puzzle-piece.svg b/assets/vendor/heroicons/optimized/24/solid/puzzle-piece.svg new file mode 100644 index 0000000..4f85b37 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/puzzle-piece.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/qr-code.svg b/assets/vendor/heroicons/optimized/24/solid/qr-code.svg new file mode 100644 index 0000000..7f676ca --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/qr-code.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/question-mark-circle.svg b/assets/vendor/heroicons/optimized/24/solid/question-mark-circle.svg new file mode 100644 index 0000000..2ae51be --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/question-mark-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/queue-list.svg b/assets/vendor/heroicons/optimized/24/solid/queue-list.svg new file mode 100644 index 0000000..536fd88 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/queue-list.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/radio.svg b/assets/vendor/heroicons/optimized/24/solid/radio.svg new file mode 100644 index 0000000..92ca514 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/radio.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/receipt-percent.svg b/assets/vendor/heroicons/optimized/24/solid/receipt-percent.svg new file mode 100644 index 0000000..5eb6371 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/receipt-percent.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/receipt-refund.svg b/assets/vendor/heroicons/optimized/24/solid/receipt-refund.svg new file mode 100644 index 0000000..6a1a154 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/receipt-refund.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/rectangle-group.svg b/assets/vendor/heroicons/optimized/24/solid/rectangle-group.svg new file mode 100644 index 0000000..289d198 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/rectangle-group.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/rectangle-stack.svg b/assets/vendor/heroicons/optimized/24/solid/rectangle-stack.svg new file mode 100644 index 0000000..82a1334 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/rectangle-stack.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/rocket-launch.svg b/assets/vendor/heroicons/optimized/24/solid/rocket-launch.svg new file mode 100644 index 0000000..522fc64 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/rocket-launch.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/rss.svg b/assets/vendor/heroicons/optimized/24/solid/rss.svg new file mode 100644 index 0000000..b9a8ab2 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/rss.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/scale.svg b/assets/vendor/heroicons/optimized/24/solid/scale.svg new file mode 100644 index 0000000..b7e57c7 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/scale.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/scissors.svg b/assets/vendor/heroicons/optimized/24/solid/scissors.svg new file mode 100644 index 0000000..9c971b3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/scissors.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/server-stack.svg b/assets/vendor/heroicons/optimized/24/solid/server-stack.svg new file mode 100644 index 0000000..c0d3074 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/server-stack.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/server.svg b/assets/vendor/heroicons/optimized/24/solid/server.svg new file mode 100644 index 0000000..55f9aed --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/server.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/share.svg b/assets/vendor/heroicons/optimized/24/solid/share.svg new file mode 100644 index 0000000..8f45dfc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/share.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/shield-check.svg b/assets/vendor/heroicons/optimized/24/solid/shield-check.svg new file mode 100644 index 0000000..2596ace --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/shield-check.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/shield-exclamation.svg b/assets/vendor/heroicons/optimized/24/solid/shield-exclamation.svg new file mode 100644 index 0000000..ce53fcc --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/shield-exclamation.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/shopping-bag.svg b/assets/vendor/heroicons/optimized/24/solid/shopping-bag.svg new file mode 100644 index 0000000..e6503ee --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/shopping-bag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/shopping-cart.svg b/assets/vendor/heroicons/optimized/24/solid/shopping-cart.svg new file mode 100644 index 0000000..931a12f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/shopping-cart.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/signal-slash.svg b/assets/vendor/heroicons/optimized/24/solid/signal-slash.svg new file mode 100644 index 0000000..21c65cb --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/signal-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/signal.svg b/assets/vendor/heroicons/optimized/24/solid/signal.svg new file mode 100644 index 0000000..9027aef --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/signal.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/sparkles.svg b/assets/vendor/heroicons/optimized/24/solid/sparkles.svg new file mode 100644 index 0000000..0d8d0c1 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/sparkles.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/speaker-wave.svg b/assets/vendor/heroicons/optimized/24/solid/speaker-wave.svg new file mode 100644 index 0000000..bd84477 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/speaker-wave.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/speaker-x-mark.svg b/assets/vendor/heroicons/optimized/24/solid/speaker-x-mark.svg new file mode 100644 index 0000000..e71f1b6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/speaker-x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/square-2-stack.svg b/assets/vendor/heroicons/optimized/24/solid/square-2-stack.svg new file mode 100644 index 0000000..c3726a5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/square-2-stack.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/square-3-stack-3d.svg b/assets/vendor/heroicons/optimized/24/solid/square-3-stack-3d.svg new file mode 100644 index 0000000..8ed638d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/square-3-stack-3d.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/squares-2x2.svg b/assets/vendor/heroicons/optimized/24/solid/squares-2x2.svg new file mode 100644 index 0000000..475f6c3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/squares-2x2.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/squares-plus.svg b/assets/vendor/heroicons/optimized/24/solid/squares-plus.svg new file mode 100644 index 0000000..88a2dda --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/squares-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/star.svg b/assets/vendor/heroicons/optimized/24/solid/star.svg new file mode 100644 index 0000000..8509819 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/star.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/stop-circle.svg b/assets/vendor/heroicons/optimized/24/solid/stop-circle.svg new file mode 100644 index 0000000..8e57a2c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/stop-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/stop.svg b/assets/vendor/heroicons/optimized/24/solid/stop.svg new file mode 100644 index 0000000..1bf426f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/stop.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/sun.svg b/assets/vendor/heroicons/optimized/24/solid/sun.svg new file mode 100644 index 0000000..1b597fa --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/sun.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/swatch.svg b/assets/vendor/heroicons/optimized/24/solid/swatch.svg new file mode 100644 index 0000000..9b26c03 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/swatch.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/table-cells.svg b/assets/vendor/heroicons/optimized/24/solid/table-cells.svg new file mode 100644 index 0000000..151a30c --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/table-cells.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/tag.svg b/assets/vendor/heroicons/optimized/24/solid/tag.svg new file mode 100644 index 0000000..efcd01b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/tag.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/ticket.svg b/assets/vendor/heroicons/optimized/24/solid/ticket.svg new file mode 100644 index 0000000..e947c41 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/ticket.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/trash.svg b/assets/vendor/heroicons/optimized/24/solid/trash.svg new file mode 100644 index 0000000..ed7bf43 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/trash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/trophy.svg b/assets/vendor/heroicons/optimized/24/solid/trophy.svg new file mode 100644 index 0000000..ed7ee15 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/trophy.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/truck.svg b/assets/vendor/heroicons/optimized/24/solid/truck.svg new file mode 100644 index 0000000..c218da6 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/truck.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/tv.svg b/assets/vendor/heroicons/optimized/24/solid/tv.svg new file mode 100644 index 0000000..8f27026 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/tv.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/user-circle.svg b/assets/vendor/heroicons/optimized/24/solid/user-circle.svg new file mode 100644 index 0000000..978d0b8 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/user-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/user-group.svg b/assets/vendor/heroicons/optimized/24/solid/user-group.svg new file mode 100644 index 0000000..7ae7600 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/user-group.svg @@ -0,0 +1,4 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/user-minus.svg b/assets/vendor/heroicons/optimized/24/solid/user-minus.svg new file mode 100644 index 0000000..062a7c9 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/user-minus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/user-plus.svg b/assets/vendor/heroicons/optimized/24/solid/user-plus.svg new file mode 100644 index 0000000..ef313fa --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/user-plus.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/user.svg b/assets/vendor/heroicons/optimized/24/solid/user.svg new file mode 100644 index 0000000..207213d --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/user.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/users.svg b/assets/vendor/heroicons/optimized/24/solid/users.svg new file mode 100644 index 0000000..2959115 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/users.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/variable.svg b/assets/vendor/heroicons/optimized/24/solid/variable.svg new file mode 100644 index 0000000..5601cac --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/variable.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/video-camera-slash.svg b/assets/vendor/heroicons/optimized/24/solid/video-camera-slash.svg new file mode 100644 index 0000000..2a344d5 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/video-camera-slash.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/video-camera.svg b/assets/vendor/heroicons/optimized/24/solid/video-camera.svg new file mode 100644 index 0000000..55bf7b4 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/video-camera.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/view-columns.svg b/assets/vendor/heroicons/optimized/24/solid/view-columns.svg new file mode 100644 index 0000000..f7295e3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/view-columns.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/viewfinder-circle.svg b/assets/vendor/heroicons/optimized/24/solid/viewfinder-circle.svg new file mode 100644 index 0000000..e04b727 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/viewfinder-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/wallet.svg b/assets/vendor/heroicons/optimized/24/solid/wallet.svg new file mode 100644 index 0000000..001b38a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/wallet.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/wifi.svg b/assets/vendor/heroicons/optimized/24/solid/wifi.svg new file mode 100644 index 0000000..eb4fd4e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/wifi.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/window.svg b/assets/vendor/heroicons/optimized/24/solid/window.svg new file mode 100644 index 0000000..4de83b3 --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/window.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/wrench-screwdriver.svg b/assets/vendor/heroicons/optimized/24/solid/wrench-screwdriver.svg new file mode 100644 index 0000000..b7e0e9e --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/wrench-screwdriver.svg @@ -0,0 +1,5 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/wrench.svg b/assets/vendor/heroicons/optimized/24/solid/wrench.svg new file mode 100644 index 0000000..3b61d5b --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/wrench.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/x-circle.svg b/assets/vendor/heroicons/optimized/24/solid/x-circle.svg new file mode 100644 index 0000000..913782a --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/x-circle.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/heroicons/optimized/24/solid/x-mark.svg b/assets/vendor/heroicons/optimized/24/solid/x-mark.svg new file mode 100644 index 0000000..e525a8f --- /dev/null +++ b/assets/vendor/heroicons/optimized/24/solid/x-mark.svg @@ -0,0 +1,3 @@ + diff --git a/assets/vendor/topbar.js b/assets/vendor/topbar.js new file mode 100644 index 0000000..c560d29 --- /dev/null +++ b/assets/vendor/topbar.js @@ -0,0 +1,165 @@ +/** + * @license MIT + * topbar 2.0.0, 2023-02-04 + * https://buunguyen.github.io/topbar + * Copyright (c) 2021 Buu Nguyen + */ +;(function (window, document) { + "use strict" + + // https://gist.github.com/paulirish/1579671 + ;(function () { + var lastTime = 0 + var vendors = ["ms", "moz", "webkit", "o"] + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x] + "RequestAnimationFrame"] + window.cancelAnimationFrame = + window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"] + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime() + var timeToCall = Math.max(0, 16 - (currTime - lastTime)) + var id = window.setTimeout(function () { + callback(currTime + timeToCall) + }, timeToCall) + lastTime = currTime + timeToCall + return id + } + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id) + } + })() + + var canvas, + currentProgress, + showing, + progressTimerId = null, + fadeTimerId = null, + delayTimerId = null, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false) + else if (elem.attachEvent) elem.attachEvent("on" + type, handler) + else elem["on" + type] = handler + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth + canvas.height = options.barThickness * 5 // need space for shadow + + var ctx = canvas.getContext("2d") + ctx.shadowBlur = options.shadowBlur + ctx.shadowColor = options.shadowColor + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0) + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]) + ctx.lineWidth = options.barThickness + ctx.beginPath() + ctx.moveTo(0, options.barThickness / 2) + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ) + ctx.strokeStyle = lineGradient + ctx.stroke() + }, + createCanvas = function () { + canvas = document.createElement("canvas") + var style = canvas.style + style.position = "fixed" + style.top = style.left = style.right = style.margin = style.padding = 0 + style.zIndex = 100001 + style.display = "none" + if (options.className) canvas.classList.add(options.className) + document.body.appendChild(canvas) + addEvent(window, "resize", repaint) + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key] + }, + show: function (delay) { + if (showing) return + if (delay) { + if (delayTimerId) return + delayTimerId = setTimeout(() => topbar.show(), delay) + } else { + showing = true + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId) + if (!canvas) createCanvas() + canvas.style.opacity = 1 + canvas.style.display = "block" + topbar.progress(0) + if (options.autoRun) { + ;(function loop() { + progressTimerId = window.requestAnimationFrame(loop) + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ) + })() + } + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to) + } + currentProgress = to > 1 ? 1 : to + repaint() + return currentProgress + }, + hide: function () { + clearTimeout(delayTimerId) + delayTimerId = null + if (!showing) return + showing = false + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId) + progressTimerId = null + } + ;(function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05 + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none" + fadeTimerId = null + return + } + } + fadeTimerId = window.requestAnimationFrame(loop) + })() + }, + } + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar + }) + } else { + this.topbar = topbar + } +}).call(this, window, document) diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..92289c5 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,67 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +config :portfolio, + generators: [timestamp_type: :utc_datetime] + +# Configures the endpoint +config :portfolio, PortfolioWeb.Endpoint, + url: [host: "localhost"], + adapter: Phoenix.Endpoint.Cowboy2Adapter, + render_errors: [ + formats: [html: PortfolioWeb.ErrorHTML, json: PortfolioWeb.ErrorJSON], + layout: false + ], + pubsub_server: Portfolio.PubSub, + live_view: [signing_salt: "4yPvQNNr"] + +# Configures the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :portfolio, Portfolio.Mailer, adapter: Swoosh.Adapters.Local + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.19.7", + default: [ + args: + ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ], + path: System.get_env("MIX_ESBUILD_PATH") + +# Configure tailwind (the version is required) +config :tailwind, + version: "3.3.2", + default: [ + args: ~w( + --config=tailwind.config.js + --input=css/app.css + --output=../priv/static/assets/app.css + ), + cd: Path.expand("../assets", __DIR__) + ], + path: System.get_env("MIX_TAILWIND_PATH") + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs new file mode 100644 index 0000000..df267ea --- /dev/null +++ b/config/dev.exs @@ -0,0 +1,72 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we can use it +# to bundle .js and .css sources. +config :portfolio, PortfolioWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "YYlTshfOflSHl7x+Z4EuzrbrIAJdvbQz1/aZp/MkXrwbzsJo/c+p2FVSWto6sNik", + watchers: [ + esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}, + tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :portfolio, PortfolioWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg|pdf)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/portfolio_web/(controllers|live|components)/.*(ex|heex)$" + ] + ] + +# Enable dev routes for dashboard and mailbox +config :portfolio, dev_routes: true + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime + +# Include HEEx debug annotations as HTML comments in rendered markup +config :phoenix_live_view, :debug_heex_annotations, true + +# Disable swoosh api client as it is only required for production adapters. +config :swoosh, :api_client, false diff --git a/config/prod.exs b/config/prod.exs new file mode 100644 index 0000000..9bf09f6 --- /dev/null +++ b/config/prod.exs @@ -0,0 +1,20 @@ +import Config + +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix assets.deploy` task, +# which you should run after static files are built and +# before starting your production server. +config :portfolio, PortfolioWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" + +# Configures Swoosh API Client +config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Portfolio.Finch + +# Disable Swoosh Local Memory Storage +config :swoosh, local: false + +# Do not print debug messages in production +config :logger, level: :info + +# Runtime production configuration, including reading +# of environment variables, is done on config/runtime.exs. diff --git a/config/runtime.exs b/config/runtime.exs new file mode 100644 index 0000000..24f8ae5 --- /dev/null +++ b/config/runtime.exs @@ -0,0 +1,102 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/portfolio start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :portfolio, PortfolioWeb.Endpoint, server: true +end + +if config_env() == :prod do + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + port = String.to_integer(System.get_env("PORT") || "4000") + + config :portfolio, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") + + config :portfolio, PortfolioWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base + + # ## SSL Support + # + # To get SSL working, you will need to add the `https` key + # to your endpoint configuration: + # + # config :portfolio, PortfolioWeb.Endpoint, + # https: [ + # ..., + # port: 443, + # cipher_suite: :strong, + # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), + # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") + # ] + # + # The `cipher_suite` is set to `:strong` to support only the + # latest and more secure SSL ciphers. This means old browsers + # and clients may not be supported. You can set it to + # `:compatible` for wider support. + # + # `:keyfile` and `:certfile` expect an absolute path to the key + # and cert in disk or a relative path inside priv, for example + # "priv/ssl/server.key". For all supported SSL configuration + # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 + # + # We also recommend setting `force_ssl` in your endpoint, ensuring + # no data is ever sent via http, always redirecting to https: + # + # config :portfolio, PortfolioWeb.Endpoint, + # force_ssl: [hsts: true] + # + # Check `Plug.SSL` for all available options in `force_ssl`. + + # ## Configuring the mailer + # + # In production you need to configure the mailer to use a different adapter. + # Also, you may need to configure the Swoosh API client of your choice if you + # are not using SMTP. Here is an example of the configuration: + # + # config :portfolio, Portfolio.Mailer, + # adapter: Swoosh.Adapters.Mailgun, + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("MAILGUN_DOMAIN") + # + # For this example you need include a HTTP client required by Swoosh API client. + # Swoosh supports Hackney and Finch out of the box: + # + # config :swoosh, :api_client, Swoosh.ApiClient.Hackney + # + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. +end diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..5cad98a --- /dev/null +++ b/config/test.exs @@ -0,0 +1,20 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :portfolio, PortfolioWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "+DbOkmCUYtnqD4Ucib/JfWwcifO8TP9Vo1YHQWPRby5pqWKkMUcFcU57ioKlxNtv", + server: false + +# In test we don't send emails. +config :portfolio, Portfolio.Mailer, adapter: Swoosh.Adapters.Test + +# Disable swoosh api client as it is only required for production adapters. +config :swoosh, :api_client, false + +# Print only warnings and errors during test +config :logger, level: :warning + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..f620865 --- /dev/null +++ b/default.nix @@ -0,0 +1,10 @@ +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).defaultNix diff --git a/deps.nix b/deps.nix new file mode 100644 index 0000000..44bdc42 --- /dev/null +++ b/deps.nix @@ -0,0 +1,467 @@ +{ lib, beamPackages, overrides ? (x: y: {}) }: + +let + buildRebar3 = lib.makeOverridable beamPackages.buildRebar3; + buildMix = lib.makeOverridable beamPackages.buildMix; + buildErlangMk = lib.makeOverridable beamPackages.buildErlangMk; + + self = packages // (overrides self packages); + + packages = with beamPackages; with self; { + castore = buildMix rec { + name = "castore"; + version = "1.0.7"; + + src = fetchHex { + pkg = "castore"; + version = "${version}"; + sha256 = "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"; + }; + + beamDeps = []; + }; + + cowboy = buildErlangMk rec { + name = "cowboy"; + version = "2.12.0"; + + src = fetchHex { + pkg = "cowboy"; + version = "${version}"; + sha256 = "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"; + }; + + beamDeps = [ cowlib ranch ]; + }; + + cowboy_telemetry = buildRebar3 rec { + name = "cowboy_telemetry"; + version = "0.4.0"; + + src = fetchHex { + pkg = "cowboy_telemetry"; + version = "${version}"; + sha256 = "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"; + }; + + beamDeps = [ cowboy telemetry ]; + }; + + cowlib = buildRebar3 rec { + name = "cowlib"; + version = "2.13.0"; + + src = fetchHex { + pkg = "cowlib"; + version = "${version}"; + sha256 = "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"; + }; + + beamDeps = []; + }; + + dns_cluster = buildMix rec { + name = "dns_cluster"; + version = "0.1.3"; + + src = fetchHex { + pkg = "dns_cluster"; + version = "${version}"; + sha256 = "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"; + }; + + beamDeps = []; + }; + + esbuild = buildMix rec { + name = "esbuild"; + version = "0.8.1"; + + src = fetchHex { + pkg = "esbuild"; + version = "${version}"; + sha256 = "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"; + }; + + beamDeps = [ castore jason ]; + }; + + expo = buildMix rec { + name = "expo"; + version = "0.5.2"; + + src = fetchHex { + pkg = "expo"; + version = "${version}"; + sha256 = "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"; + }; + + beamDeps = []; + }; + + file_system = buildMix rec { + name = "file_system"; + version = "1.0.0"; + + src = fetchHex { + pkg = "file_system"; + version = "${version}"; + sha256 = "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"; + }; + + beamDeps = []; + }; + + finch = buildMix rec { + name = "finch"; + version = "0.18.0"; + + src = fetchHex { + pkg = "finch"; + version = "${version}"; + sha256 = "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"; + }; + + beamDeps = [ castore mime mint nimble_options nimble_pool telemetry ]; + }; + + floki = buildMix rec { + name = "floki"; + version = "0.36.2"; + + src = fetchHex { + pkg = "floki"; + version = "${version}"; + sha256 = "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"; + }; + + beamDeps = []; + }; + + gettext = buildMix rec { + name = "gettext"; + version = "0.24.0"; + + src = fetchHex { + pkg = "gettext"; + version = "${version}"; + sha256 = "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"; + }; + + beamDeps = [ expo ]; + }; + + hpax = buildMix rec { + name = "hpax"; + version = "0.2.0"; + + src = fetchHex { + pkg = "hpax"; + version = "${version}"; + sha256 = "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"; + }; + + beamDeps = []; + }; + + jason = buildMix rec { + name = "jason"; + version = "1.4.1"; + + src = fetchHex { + pkg = "jason"; + version = "${version}"; + sha256 = "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"; + }; + + beamDeps = []; + }; + + mime = buildMix rec { + name = "mime"; + version = "2.0.5"; + + src = fetchHex { + pkg = "mime"; + version = "${version}"; + sha256 = "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"; + }; + + beamDeps = []; + }; + + mint = buildMix rec { + name = "mint"; + version = "1.6.0"; + + src = fetchHex { + pkg = "mint"; + version = "${version}"; + sha256 = "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"; + }; + + beamDeps = [ castore hpax ]; + }; + + nimble_options = buildMix rec { + name = "nimble_options"; + version = "1.1.0"; + + src = fetchHex { + pkg = "nimble_options"; + version = "${version}"; + sha256 = "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"; + }; + + beamDeps = []; + }; + + nimble_pool = buildMix rec { + name = "nimble_pool"; + version = "1.1.0"; + + src = fetchHex { + pkg = "nimble_pool"; + version = "${version}"; + sha256 = "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"; + }; + + beamDeps = []; + }; + + phoenix = buildMix rec { + name = "phoenix"; + version = "1.7.12"; + + src = fetchHex { + pkg = "phoenix"; + version = "${version}"; + sha256 = "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"; + }; + + beamDeps = [ castore jason phoenix_pubsub phoenix_template plug plug_cowboy plug_crypto telemetry websock_adapter ]; + }; + + phoenix_html = buildMix rec { + name = "phoenix_html"; + version = "3.3.3"; + + src = fetchHex { + pkg = "phoenix_html"; + version = "${version}"; + sha256 = "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"; + }; + + beamDeps = [ plug ]; + }; + + phoenix_live_dashboard = buildMix rec { + name = "phoenix_live_dashboard"; + version = "0.8.3"; + + src = fetchHex { + pkg = "phoenix_live_dashboard"; + version = "${version}"; + sha256 = "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"; + }; + + beamDeps = [ mime phoenix_live_view telemetry_metrics ]; + }; + + phoenix_live_reload = buildMix rec { + name = "phoenix_live_reload"; + version = "1.5.3"; + + src = fetchHex { + pkg = "phoenix_live_reload"; + version = "${version}"; + sha256 = "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"; + }; + + beamDeps = [ file_system phoenix ]; + }; + + phoenix_live_view = buildMix rec { + name = "phoenix_live_view"; + version = "0.20.14"; + + src = fetchHex { + pkg = "phoenix_live_view"; + version = "${version}"; + sha256 = "82f6d006c5264f979ed5eb75593d808bbe39020f20df2e78426f4f2d570e2402"; + }; + + beamDeps = [ floki jason phoenix phoenix_html phoenix_template plug telemetry ]; + }; + + phoenix_pubsub = buildMix rec { + name = "phoenix_pubsub"; + version = "2.1.3"; + + src = fetchHex { + pkg = "phoenix_pubsub"; + version = "${version}"; + sha256 = "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"; + }; + + beamDeps = []; + }; + + phoenix_template = buildMix rec { + name = "phoenix_template"; + version = "1.0.4"; + + src = fetchHex { + pkg = "phoenix_template"; + version = "${version}"; + sha256 = "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"; + }; + + beamDeps = [ phoenix_html ]; + }; + + plug = buildMix rec { + name = "plug"; + version = "1.15.3"; + + src = fetchHex { + pkg = "plug"; + version = "${version}"; + sha256 = "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"; + }; + + beamDeps = [ mime plug_crypto telemetry ]; + }; + + plug_cowboy = buildMix rec { + name = "plug_cowboy"; + version = "2.7.1"; + + src = fetchHex { + pkg = "plug_cowboy"; + version = "${version}"; + sha256 = "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"; + }; + + beamDeps = [ cowboy cowboy_telemetry plug ]; + }; + + plug_crypto = buildMix rec { + name = "plug_crypto"; + version = "2.1.0"; + + src = fetchHex { + pkg = "plug_crypto"; + version = "${version}"; + sha256 = "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"; + }; + + beamDeps = []; + }; + + ranch = buildRebar3 rec { + name = "ranch"; + version = "1.8.0"; + + src = fetchHex { + pkg = "ranch"; + version = "${version}"; + sha256 = "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"; + }; + + beamDeps = []; + }; + + swoosh = buildMix rec { + name = "swoosh"; + version = "1.16.5"; + + src = fetchHex { + pkg = "swoosh"; + version = "${version}"; + sha256 = "b2324cf696b09ee52e5e1049dcc77880a11fe618a381e2df1c5ca5d69c380eb0"; + }; + + beamDeps = [ cowboy finch jason mime plug plug_cowboy telemetry ]; + }; + + tailwind = buildMix rec { + name = "tailwind"; + version = "0.2.2"; + + src = fetchHex { + pkg = "tailwind"; + version = "${version}"; + sha256 = "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"; + }; + + beamDeps = [ castore ]; + }; + + telemetry = buildRebar3 rec { + name = "telemetry"; + version = "1.2.1"; + + src = fetchHex { + pkg = "telemetry"; + version = "${version}"; + sha256 = "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"; + }; + + beamDeps = []; + }; + + telemetry_metrics = buildMix rec { + name = "telemetry_metrics"; + version = "0.6.2"; + + src = fetchHex { + pkg = "telemetry_metrics"; + version = "${version}"; + sha256 = "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"; + }; + + beamDeps = [ telemetry ]; + }; + + telemetry_poller = buildRebar3 rec { + name = "telemetry_poller"; + version = "1.1.0"; + + src = fetchHex { + pkg = "telemetry_poller"; + version = "${version}"; + sha256 = "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"; + }; + + beamDeps = [ telemetry ]; + }; + + websock = buildMix rec { + name = "websock"; + version = "0.5.3"; + + src = fetchHex { + pkg = "websock"; + version = "${version}"; + sha256 = "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"; + }; + + beamDeps = []; + }; + + websock_adapter = buildMix rec { + name = "websock_adapter"; + version = "0.5.6"; + + src = fetchHex { + pkg = "websock_adapter"; + version = "${version}"; + sha256 = "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"; + }; + + beamDeps = [ plug plug_cowboy websock ]; + }; + }; +in self + diff --git a/flake.lock b/flake.lock index 62fd956..035b7da 100644 --- a/flake.lock +++ b/flake.lock @@ -19,11 +19,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "owner": "numtide", "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "type": "github" }, "original": { @@ -34,11 +34,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1702312524, - "narHash": "sha256-gkZJRDBUCpTPBvQk25G0B7vfbpEYM5s5OZqghkjZsnE=", + "lastModified": 1700794826, + "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a9bf124c46ef298113270b1f84a164865987a91c", + "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 73f85bb..59b3fd4 100644 --- a/flake.nix +++ b/flake.nix @@ -1,11 +1,11 @@ { description = '' - An opinionated jekyll flake. + An opinionated pheonix flake. To generate a copy of this template elsewhere, install [bootstrap](https://github.com/jrpotter/bootstrap) and run: ```bash - $ bootstrap jekyll + $ bootstrap phoenix ``` ''; @@ -19,32 +19,76 @@ flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; - gems = pkgs.bundlerEnv { - name = "portfolio-gems"; - gemdir = ./.; - ruby = pkgs.ruby_3_2; - }; + + inherit (pkgs.beam.packages.erlang_25) + beamPackages + elixir + elixir-ls + hex + mixRelease; + + nodeDependencies = ( + pkgs.callPackage ./assets/default.nix {} + ).nodeDependencies; + + tailwindcss = pkgs.nodePackages.tailwindcss.overrideAttrs (oa: { + plugins = [ + pkgs.nodePackages."@tailwindcss/forms" + ]; + }); in { packages = { - app = pkgs.stdenv.mkDerivation { - name = "portfolio"; - buildInputs = [ gems gems.wrappedRuby ]; + app = mixRelease { + pname = "portfolio"; src = ./.; version = "0.1.0"; - installPhase = "JEKYLL_ENV=production jekyll b -d $out"; + mixNixDeps = import ./deps.nix { + lib = pkgs.lib; + inherit beamPackages; + }; + + # Enable if using distributed Erlang. + # https://github.com/NixOS/nixpkgs/issues/166229 + removeCookie = false; + + # https://hexdocs.pm/esbuild/Esbuild.html + # https://hexdocs.pm/tailwind/Tailwind.html + postBuild = '' + ln -s ${nodeDependencies}/lib/node_modules assets/node_modules + export MIX_ESBUILD_PATH=${pkgs.esbuild}/bin/esbuild + export MIX_TAILWIND_PATH=${tailwindcss}/bin/tailwind + mix do deps.loadpaths --no-deps-check, assets.deploy + mix phx.gen.release + ''; }; default = self.packages.${system}.app; }; devShells.default = pkgs.mkShell { - packages = with pkgs; [ - bundix - gems - gems.wrappedRuby - ]; + packages = [ + elixir + elixir-ls + hex + ] ++ (with pkgs; [ + inotify-tools # For file watching in development. + mix2nix + node2nix + nodePackages.prettier + nodePackages.typescript-language-server + nodejs + pandoc + postgresql_15 + typescript + ]); + shellHook = '' + # The server will try to use the data directory named by this + # environment variable whenever `-D` is not specified (for e.g. + # `postgres` and `pg_ctl`). + # https://www.postgresql.org/docs/15/server-start.html + export PGDATA="$PWD/db" + ''; }; - } - ); + }); } diff --git a/gemset.nix b/gemset.nix deleted file mode 100644 index aa4816f..0000000 --- a/gemset.nix +++ /dev/null @@ -1,380 +0,0 @@ -{ - addressable = { - dependencies = ["public_suffix"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0irbdwkkjwzajq1ip6ba46q49sxnrl2cw7ddkdhsfhb6aprnm3vr"; - type = "gem"; - }; - version = "2.8.6"; - }; - colorator = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0f7wvpam948cglrciyqd798gdc6z3cfijciavd0dfixgaypmvy72"; - type = "gem"; - }; - version = "1.1.0"; - }; - concurrent-ruby = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0krcwb6mn0iklajwngwsg850nk8k9b35dhmc2qkbdqvmifdi2y9q"; - type = "gem"; - }; - version = "1.2.2"; - }; - em-websocket = { - dependencies = ["eventmachine" "http_parser.rb"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1a66b0kjk6jx7pai9gc7i27zd0a128gy73nmas98gjz6wjyr4spm"; - type = "gem"; - }; - version = "0.5.3"; - }; - eventmachine = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0wh9aqb0skz80fhfn66lbpr4f86ya2z5rx6gm5xlfhd05bj1ch4r"; - type = "gem"; - }; - version = "1.2.7"; - }; - ffi = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1yvii03hcgqj30maavddqamqy50h7y6xcn2wcyq72wn823zl4ckd"; - type = "gem"; - }; - version = "1.16.3"; - }; - forwardable-extended = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "15zcqfxfvsnprwm8agia85x64vjzr2w0xn9vxfnxzgcv8s699v0v"; - type = "gem"; - }; - version = "2.6.0"; - }; - google-protobuf = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "18yiqq657lbqbrbdfbxfspdrkiynd0wf49l3cgdw939z36cy0h77"; - type = "gem"; - }; - version = "3.25.1"; - }; - "http_parser.rb" = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1gj4fmls0mf52dlr928gaq0c0cb0m3aqa9kaa6l0ikl2zbqk42as"; - type = "gem"; - }; - version = "0.8.0"; - }; - i18n = { - dependencies = ["concurrent-ruby"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0qaamqsh5f3szhcakkak8ikxlzxqnv49n2p7504hcz2l0f4nj0wx"; - type = "gem"; - }; - version = "1.14.1"; - }; - jekyll = { - dependencies = ["addressable" "colorator" "em-websocket" "i18n" "jekyll-sass-converter" "jekyll-watch" "kramdown" "kramdown-parser-gfm" "liquid" "mercenary" "pathutil" "rouge" "safe_yaml" "terminal-table" "webrick"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0wbp9xjnjv832ksqs816napy6amp5fh8v4wbrxlpxvgakqz6scsx"; - type = "gem"; - }; - version = "4.3.2"; - }; - jekyll-feed = { - dependencies = ["jekyll"]; - groups = ["jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1hzwmjrxi57x68i7jx5rxi8qlcbqcbg3di55wywrp53pr0bap6k8"; - type = "gem"; - }; - version = "0.17.0"; - }; - jekyll-paginate = { - groups = ["default"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0r7bcs8fq98zldih4787zk5i9w24nz5wa26m84ssja95n3sas2l8"; - type = "gem"; - }; - version = "1.1.0"; - }; - jekyll-paginate-v2 = { - dependencies = ["jekyll"]; - groups = ["jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1qzlqhpiqz28624fp0ak76hfy7908w6kpx62v7z43aiwjv0yc6q0"; - type = "gem"; - }; - version = "3.0.0"; - }; - jekyll-sass-converter = { - dependencies = ["sass-embedded"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "00n9v19h0qgjijygfdkdh2gwpmdlz49nw1mqk6fnp43f317ngrz2"; - type = "gem"; - }; - version = "3.0.0"; - }; - jekyll-seo-tag = { - dependencies = ["jekyll"]; - groups = ["default"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0638mqhqynghnlnaz0xi1kvnv53wkggaq94flfzlxwandn8x2biz"; - type = "gem"; - }; - version = "2.8.0"; - }; - jekyll-sitemap = { - dependencies = ["jekyll"]; - groups = ["default"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0622rwsn5i0m5xcyzdn86l68wgydqwji03lqixdfm1f1xdfqrq0d"; - type = "gem"; - }; - version = "1.4.0"; - }; - jekyll-watch = { - dependencies = ["listen"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1qd7hy1kl87fl7l0frw5qbn22x7ayfzlv9a5ca1m59g0ym1ysi5w"; - type = "gem"; - }; - version = "2.2.1"; - }; - kramdown = { - dependencies = ["rexml"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1ic14hdcqxn821dvzki99zhmcy130yhv5fqfffkcf87asv5mnbmn"; - type = "gem"; - }; - version = "2.4.0"; - }; - kramdown-parser-gfm = { - dependencies = ["kramdown"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0a8pb3v951f4x7h968rqfsa19c8arz21zw1vaj42jza22rap8fgv"; - type = "gem"; - }; - version = "1.1.0"; - }; - lagrange = { - dependencies = ["jekyll" "jekyll-feed" "jekyll-paginate" "jekyll-seo-tag" "jekyll-sitemap"]; - groups = ["default"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1x94cw219d8pwqmsm9dp9swvsfsxpdph1d4sninbr9anc83yndh1"; - type = "gem"; - }; - version = "4.0.0"; - }; - liquid = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1czxv2i1gv3k7hxnrgfjb0z8khz74l4pmfwd70c7kr25l2qypksg"; - type = "gem"; - }; - version = "4.0.4"; - }; - listen = { - dependencies = ["rb-fsevent" "rb-inotify"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "13rgkfar8pp31z1aamxf5y7cfq88wv6rxxcwy7cmm177qq508ycn"; - type = "gem"; - }; - version = "3.8.0"; - }; - mercenary = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0f2i827w4lmsizrxixsrv2ssa3gk1b7lmqh8brk8ijmdb551wnmj"; - type = "gem"; - }; - version = "0.4.0"; - }; - pathutil = { - dependencies = ["forwardable-extended"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "12fm93ljw9fbxmv2krki5k5wkvr7560qy8p4spvb9jiiaqv78fz4"; - type = "gem"; - }; - version = "0.16.2"; - }; - public_suffix = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1bni4qjrsh2q49pnmmd6if4iv3ak36bd2cckrs6npl111n769k9m"; - type = "gem"; - }; - version = "5.0.4"; - }; - rake = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1ilr853hawi09626axx0mps4rkkmxcs54mapz9jnqvpnlwd3wsmy"; - type = "gem"; - }; - version = "13.1.0"; - }; - rb-fsevent = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1zmf31rnpm8553lqwibvv3kkx0v7majm1f341xbxc0bk5sbhp423"; - type = "gem"; - }; - version = "0.11.2"; - }; - rb-inotify = { - dependencies = ["ffi"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1jm76h8f8hji38z3ggf4bzi8vps6p7sagxn3ab57qc0xyga64005"; - type = "gem"; - }; - version = "0.10.1"; - }; - rexml = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "05i8518ay14kjbma550mv0jm8a6di8yp5phzrd8rj44z9qnrlrp0"; - type = "gem"; - }; - version = "3.2.6"; - }; - rouge = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1fkfa0iq3r9b0zzrxpxha17avmyzci3kidzmfbf6fd1279mndpb0"; - type = "gem"; - }; - version = "4.2.0"; - }; - safe_yaml = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "0j7qv63p0vqcd838i2iy2f76c3dgwzkiz1d1xkg7n0pbnxj2vb56"; - type = "gem"; - }; - version = "1.0.5"; - }; - sass-embedded = { - dependencies = ["google-protobuf" "rake"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "10f04wvgca22lynvy4pycabkf55p4jf3a3bhmmscjmxv89g9khpg"; - type = "gem"; - }; - version = "1.69.5"; - }; - terminal-table = { - dependencies = ["unicode-display_width"]; - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "14dfmfjppmng5hwj7c5ka6qdapawm3h6k9lhn8zj001ybypvclgr"; - type = "gem"; - }; - version = "3.0.2"; - }; - unicode-display_width = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1d0azx233nags5jx3fqyr23qa2rhgzbhv8pxp46dgbg1mpf82xky"; - type = "gem"; - }; - version = "2.5.0"; - }; - webrick = { - groups = ["default" "jekyll_plugins"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "13qm7s0gr2pmfcl7dxrmq38asaza4w0i2n9my4yzs499j731wh8r"; - type = "gem"; - }; - version = "1.8.1"; - }; -} diff --git a/index.html b/index.html deleted file mode 100644 index cc8c86b..0000000 --- a/index.html +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: home -title: Home -pagination: - enabled: true - category: home - indexpage: index ---- diff --git a/lib/portfolio.ex b/lib/portfolio.ex new file mode 100644 index 0000000..166e3d1 --- /dev/null +++ b/lib/portfolio.ex @@ -0,0 +1,9 @@ +defmodule Portfolio do + @moduledoc """ + Portfolio keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/lib/portfolio/application.ex b/lib/portfolio/application.ex new file mode 100644 index 0000000..69edb9f --- /dev/null +++ b/lib/portfolio/application.ex @@ -0,0 +1,35 @@ +defmodule Portfolio.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + PortfolioWeb.Telemetry, + {DNSCluster, query: Application.get_env(:portfolio, :dns_cluster_query) || :ignore}, + {Phoenix.PubSub, name: Portfolio.PubSub}, + # Start the Finch HTTP client for sending emails + {Finch, name: Portfolio.Finch}, + # Start a worker by calling: Portfolio.Worker.start_link(arg) + # {Portfolio.Worker, arg}, + # Start to serve requests, typically the last entry + PortfolioWeb.Endpoint + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Portfolio.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + PortfolioWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/lib/portfolio/mailer.ex b/lib/portfolio/mailer.ex new file mode 100644 index 0000000..dc8a8ea --- /dev/null +++ b/lib/portfolio/mailer.ex @@ -0,0 +1,3 @@ +defmodule Portfolio.Mailer do + use Swoosh.Mailer, otp_app: :portfolio +end diff --git a/lib/portfolio_web.ex b/lib/portfolio_web.ex new file mode 100644 index 0000000..cd07eb2 --- /dev/null +++ b/lib/portfolio_web.ex @@ -0,0 +1,114 @@ +defmodule PortfolioWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, components, channels, and so on. + + This can be used in your application as: + + use PortfolioWeb, :controller + use PortfolioWeb, :html + + The definitions below will be executed for every controller, + component, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define additional modules and import + those modules here. + """ + + def static_paths, do: ~w(assets files fonts images favicon.ico robots.txt) + + def router do + quote do + use Phoenix.Router, helpers: false + + # Import common connection and controller functions to use in pipelines + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + end + end + + def controller do + quote do + use Phoenix.Controller, + formats: [:html, :json], + layouts: [html: PortfolioWeb.Layouts] + + import Plug.Conn + import PortfolioWeb.Gettext + + unquote(verified_routes()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {PortfolioWeb.Layouts, :app} + + unquote(html_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(html_helpers()) + end + end + + def html do + quote do + use Phoenix.Component + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_csrf_token: 0, view_module: 1, view_template: 1] + + # Include general helpers for rendering HTML + unquote(html_helpers()) + end + end + + defp html_helpers do + quote do + # HTML escaping functionality + import Phoenix.HTML + # UI components and translation + import PortfolioWeb.CoreComponents + import PortfolioWeb.CustomComponents + import PortfolioWeb.Gettext + + # Shortcut for generating JS commands + alias Phoenix.LiveView.JS + + # Routes generation with the ~p sigil + unquote(verified_routes()) + end + end + + def verified_routes do + quote do + use Phoenix.VerifiedRoutes, + endpoint: PortfolioWeb.Endpoint, + router: PortfolioWeb.Router, + statics: PortfolioWeb.static_paths() + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/lib/portfolio_web/components/core_components.ex b/lib/portfolio_web/components/core_components.ex new file mode 100644 index 0000000..b6fdbf0 --- /dev/null +++ b/lib/portfolio_web/components/core_components.ex @@ -0,0 +1,675 @@ +defmodule PortfolioWeb.CoreComponents do + @moduledoc """ + Provides core UI components. + + At first glance, this module may seem daunting, but its goal is to provide + core building blocks for your application, such as modals, tables, and forms. + The components consist mostly of markup and are well-documented with doc + strings and declarative assigns. You may customize and style them in any way + you want, based on your application growth and needs. + + The default components use Tailwind CSS, a utility-first CSS framework. See + the [Tailwind CSS documentation](https://tailwindcss.com) to learn how to + customize them or feel free to swap in another framework altogether. + + Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for + usage. + """ + use Phoenix.Component + + alias Phoenix.LiveView.JS + import PortfolioWeb.Gettext + + @doc """ + Renders a modal. + + ## Examples + + <.modal id="confirm-modal"> + This is a modal. + + + JS commands may be passed to the `:on_cancel` to configure + the closing/cancel event, for example: + + <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}> + This is another modal. + + + """ + attr :id, :string, required: true + attr :show, :boolean, default: false + attr :on_cancel, JS, default: %JS{} + slot :inner_block, required: true + + def modal(assigns) do + ~H""" + + """ + end + + def input(%{type: "select"} = assigns) do + ~H""" +
+ <.label for={@id}><%= @label %> + + <.error :for={msg <- @errors}><%= msg %> +
+ """ + end + + def input(%{type: "textarea"} = assigns) do + ~H""" +
+ <.label for={@id}><%= @label %> + + <.error :for={msg <- @errors}><%= msg %> +
+ """ + end + + # All other inputs text, datetime-local, url, password, etc. are handled here... + def input(assigns) do + ~H""" +
+ <.label for={@id}><%= @label %> + + <.error :for={msg <- @errors}><%= msg %> +
+ """ + end + + @doc """ + Renders a label. + """ + attr :for, :string, default: nil + slot :inner_block, required: true + + def label(assigns) do + ~H""" + + """ + end + + @doc """ + Generates a generic error message. + """ + slot :inner_block, required: true + + def error(assigns) do + ~H""" +

+ <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> + <%= render_slot(@inner_block) %> +

+ """ + end + + @doc """ + Renders a header with title. + """ + attr :class, :string, default: nil + + slot :inner_block, required: true + slot :subtitle + slot :actions + + def header(assigns) do + ~H""" +
+
+

+ <%= render_slot(@inner_block) %> +

+

+ <%= render_slot(@subtitle) %> +

+
+
<%= render_slot(@actions) %>
+
+ """ + end + + @doc ~S""" + Renders a table with generic styling. + + ## Examples + + <.table id="users" rows={@users}> + <:col :let={user} label="id"><%= user.id %> + <:col :let={user} label="username"><%= user.username %> + + """ + attr :id, :string, required: true + attr :rows, :list, required: true + attr :row_id, :any, default: nil, doc: "the function for generating the row id" + attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" + + attr :row_item, :any, + default: &Function.identity/1, + doc: "the function for mapping each row before calling the :col and :action slots" + + slot :col, required: true do + attr :label, :string + end + + slot :action, doc: "the slot for showing user actions in the last table column" + + def table(assigns) do + assigns = + with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do + assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) + end + + ~H""" +
+ + + + + + + + + + + + + +
<%= col[:label] %> + <%= gettext("Actions") %> +
+
+ + + <%= render_slot(col, @row_item.(row)) %> + +
+
+
+ + + <%= render_slot(action, @row_item.(row)) %> + +
+
+
+ """ + end + + @doc """ + Renders a data list. + + ## Examples + + <.list> + <:item title="Title"><%= @post.title %> + <:item title="Views"><%= @post.views %> + + """ + slot :item, required: true do + attr :title, :string, required: true + end + + def list(assigns) do + ~H""" +
+
+
+
<%= item.title %>
+
<%= render_slot(item) %>
+
+
+
+ """ + end + + @doc """ + Renders a back navigation link. + + ## Examples + + <.back navigate={~p"/posts"}>Back to posts + """ + attr :navigate, :any, required: true + slot :inner_block, required: true + + def back(assigns) do + ~H""" +
+ <.link + navigate={@navigate} + class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" + > + <.icon name="hero-arrow-left-solid" class="h-3 w-3" /> + <%= render_slot(@inner_block) %> + +
+ """ + end + + @doc """ + Renders a [Heroicon](https://heroicons.com). + + Heroicons come in three styles – outline, solid, and mini. + By default, the outline style is used, but solid and mini may + be applied by using the `-solid` and `-mini` suffix. + + You can customize the size and colors of the icons by setting + width, height, and background color classes. + + Icons are extracted from your `assets/vendor/heroicons` directory and bundled + within your compiled app.css by the plugin in your `assets/tailwind.config.js`. + + ## Examples + + <.icon name="hero-x-mark-solid" /> + <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" /> + """ + attr :name, :string, required: true + attr :class, :string, default: nil + + def icon(%{name: "hero-" <> _} = assigns) do + ~H""" + + """ + end + + ## JS Commands + + def show(js \\ %JS{}, selector) do + JS.show(js, + to: selector, + transition: + {"transition-all transform ease-out duration-300", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", + "opacity-100 translate-y-0 sm:scale-100"} + ) + end + + def hide(js \\ %JS{}, selector) do + JS.hide(js, + to: selector, + time: 200, + transition: + {"transition-all transform ease-in duration-200", + "opacity-100 translate-y-0 sm:scale-100", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} + ) + end + + def show_modal(js \\ %JS{}, id) when is_binary(id) do + js + |> JS.show(to: "##{id}") + |> JS.show( + to: "##{id}-bg", + transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"} + ) + |> show("##{id}-container") + |> JS.add_class("overflow-hidden", to: "body") + |> JS.focus_first(to: "##{id}-content") + end + + def hide_modal(js \\ %JS{}, id) do + js + |> JS.hide( + to: "##{id}-bg", + transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"} + ) + |> hide("##{id}-container") + |> JS.hide(to: "##{id}", transition: {"block", "block", "hidden"}) + |> JS.remove_class("overflow-hidden", to: "body") + |> JS.pop_focus() + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # However the error messages in our forms and APIs are generated + # dynamically, so we need to translate them by calling Gettext + # with our gettext backend as first argument. Translations are + # available in the errors.po file (as we use the "errors" domain). + if count = opts[:count] do + Gettext.dngettext(BlahWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(BlahWeb.Gettext, "errors", msg, opts) + end + end + + @doc """ + Translates the errors for a field from a keyword list of errors. + """ + def translate_errors(errors, field) when is_list(errors) do + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) + end +end diff --git a/lib/portfolio_web/components/custom_components.ex b/lib/portfolio_web/components/custom_components.ex new file mode 100644 index 0000000..ae641b9 --- /dev/null +++ b/lib/portfolio_web/components/custom_components.ex @@ -0,0 +1,135 @@ +defmodule PortfolioWeb.CustomComponents do + use Phoenix.Component + + import PortfolioWeb.CoreComponents + + @doc """ + Renders an anchor tag that opens a new tab. + + ## Examples + + <.link_blank href="/files/tagless-final-parsing/kiselyov-interpreters.pdf"> + PDF + , + """ + attr :href, :string, required: true + slot :inner_block, required: true + + def link_blank(assigns) do + ~H""" + + <%= render_slot(@inner_block) %> + + """ + end + + @doc """ + Renders a project description. + """ + attr :title, :string, required: true + attr :href, :string, required: true + attr :date, :string, required: true + slot :inner_block, required: true + + def project(assigns) do + ~H""" +
+ <%= @title %> + <%= @date %> +

+ <%= render_slot(@inner_block) %> +

+
+ """ + end + + @doc """ + Renders a blog description. + """ + attr :title, :string, required: true + attr :href, :string, required: true + attr :date, :string, required: true + slot :inner_block, required: true + + def blog(assigns) do + ~H""" +
+ <.link navigate={@href} class="pr-1 font-bold text-cyan-500"><%= @title %> + <%= @date %> +

+ <%= render_slot(@inner_block) %> +

+
+ """ + end + + @doc """ + Renders an informational admonition. + """ + slot :inner_block, required: true + + def info(assigns) do + ~H""" + + """ + end + + @doc """ + Renders a tip admonition. + """ + slot :inner_block, required: true + + def tip(assigns) do + ~H""" + + """ + end + + @doc """ + Renders a warning admonition. + """ + slot :inner_block, required: true + + def warning(assigns) do + ~H""" + + """ + end + + @doc """ + Renders an accordion. + """ + attr :header, :string, required: true + slot :inner_block, required: true + + def accordion(assigns) do + ~H""" +
+ + <%= @header %> + +
+ <%= render_slot(@inner_block) %> +
+
+ """ + end +end diff --git a/lib/portfolio_web/components/layouts.ex b/lib/portfolio_web/components/layouts.ex new file mode 100644 index 0000000..5f408ab --- /dev/null +++ b/lib/portfolio_web/components/layouts.ex @@ -0,0 +1,5 @@ +defmodule PortfolioWeb.Layouts do + use PortfolioWeb, :html + + embed_templates "layouts/*" +end diff --git a/lib/portfolio_web/components/layouts/app.html.heex b/lib/portfolio_web/components/layouts/app.html.heex new file mode 100644 index 0000000..ab3e1a5 --- /dev/null +++ b/lib/portfolio_web/components/layouts/app.html.heex @@ -0,0 +1,9 @@ +
+ <.flash_group flash={@flash} /> +

+ Joshua Potter +

+
+ <%= @inner_content %> +
+
diff --git a/lib/portfolio_web/components/layouts/root.html.heex b/lib/portfolio_web/components/layouts/root.html.heex new file mode 100644 index 0000000..61bf719 --- /dev/null +++ b/lib/portfolio_web/components/layouts/root.html.heex @@ -0,0 +1,18 @@ + + + + + + + <.live_title suffix=" · Joshua Potter"> + <%= assigns[:page_title] || "Portfolio" %> + + + + + + + <%= @inner_content %> + + diff --git a/lib/portfolio_web/controllers/blog_controller.ex b/lib/portfolio_web/controllers/blog_controller.ex new file mode 100644 index 0000000..10d965e --- /dev/null +++ b/lib/portfolio_web/controllers/blog_controller.ex @@ -0,0 +1,11 @@ +defmodule PortfolioWeb.BlogController do + use PortfolioWeb, :controller + + def effect_systems(conn, _params) do + render(conn, :effect_systems) + end + + def tagless_final_parsing(conn, _params) do + render(conn, :tagless_final_parsing) + end +end diff --git a/lib/portfolio_web/controllers/blog_html.ex b/lib/portfolio_web/controllers/blog_html.ex new file mode 100644 index 0000000..3525853 --- /dev/null +++ b/lib/portfolio_web/controllers/blog_html.ex @@ -0,0 +1,5 @@ +defmodule PortfolioWeb.BlogHTML do + use PortfolioWeb, :html + + embed_templates "blog_html/*" +end diff --git a/lib/portfolio_web/controllers/blog_html/effect_systems.html.heex b/lib/portfolio_web/controllers/blog_html/effect_systems.html.heex new file mode 100644 index 0000000..916214a --- /dev/null +++ b/lib/portfolio_web/controllers/blog_html/effect_systems.html.heex @@ -0,0 +1,1005 @@ +
+

Effect Systems

+

20 Mar 2022

+
+

As I’ve begun exploring the world of so-called algebraic + effect systems, I’ve become increasingly frustrated in the + level of documentation around them. Learning to use them (and moreso + understanding how they work) requires diving into library internals, + watching various videos, and hoping to grok why certain effects aren’t + being interpreted the way you might have hoped. My goal in this post + is to address this issue, at least to some degree, in a focused, + pedagogical fashion.

A large portion of this post has + been derived from the implementation of the fused-effects + library, chosen because it seems to have the most active development, + the smallest dependency footprint, and minimal type machinery. In + turn, this library was largely inspired by Nicolas Wu, Tom Schrijvers, + and Ralf Hinze’s work in Effect Handlers in + Scope. As such, we’ll discuss choice parts of this paper as well. +

Code snippets can be found in this git + repository.

+
+ +
♤♠♤♠♤
+

Free Monads

+

To begin our exploration, let’s pose a few questions:

+
    +
  1. How can we go about converting a simple algebraic data type into a + monad?
  2. +
  3. Does there exist some set of minimal requirements the data type + must fulfill to make this conversion “free”?1
  4. +
+

To help guide our decision making, we’ll examine the internals of + some arbitrary monad. More concretely, let’s see what + 1 + 2 could look like within a monadic context:

+
onePlusTwo :: forall m. Monad m => m Int
+onePlusTwo = do
+  a <- pure 1
+  b <- pure 2
+  pure $ a + b
+

The above won’t win any awards, but it should be illustrative + enough for our purposes. do is just syntactic sugar for + repeated bind applications (>>=), so we could’ve + written the above alternatively as:

+
onePlusTwo' :: forall m. Monad m => m Int
+onePlusTwo' = pure 1 >>= (\a -> pure 2 >>= (\b -> pure (a + b)))
+

This is where we’ll pause for a moment and squint. We see that + do syntax desugars into something that looks awfully + close to a (non-empty) list! Let’s compare the above with how we might + define that:

+
data NonEmptyList a = Last a | Cons a (a -> NonEmptyList a)
+
+onePlusTwo'' :: NonEmptyList Int
+onePlusTwo'' = Cons 1 (\a -> Cons 2 (\b -> Last (a + b)))
+
+runNonEmptyList :: NonEmptyList Int -> Int
+runNonEmptyList (Last a) = a
+runNonEmptyList (Cons a f) = runNonEmptyList (f a)
+
+-- >>> runIdentity onePlusTwo'
+-- 3
+-- >>> runNonEmptyList onePlusTwo''
+-- 3
+

Take a moment to appreciate the rough pseudo-equivalence of + NonEmptyList and a monad. Also take a moment to + appreciate the differences. Because we no longer employ the bind + operator anywhere within our function definitions, we have effectively + separated the syntax of our original function from + its semantics. That is, onePlusTwo'' can + be viewed as a program in and of itself, and the + runNonEmptyList handler can be viewed as + the interpretation of said program.

+

Making a Monad

+

NonEmptyList was formulated from a monad, so it’s + natural to think perhaps it too is a monad. Unfortunately this is not + the case - it isn’t even a functor! Give it a try or use + {-# LANGUAGE DeriveFunctor #-} to ask the compiler to + make an attempt on your behalf.

+ <.tip> +

Type variable a is said to be contravariant with + respect to Cons. That is, a resides in a negative + position within Cons’s function.

+ +

If we can’t make this a monad as is, are there a minimal number of + changes we could introduce to make it happen? Ideally our changes + maintain the “shape” of the data type as much as possible, thereby + maintaining the integrity behind the original derivation. Since our + current roadblock stems from type variable a’s position + in Cons, let’s see what happens if we just abstract it + away:

+
data NonEmptyList' f a = Last' a | Cons' a (f (NonEmptyList' f a))
+

With general parameter f now in the place of + (->) a, a functor derivation becomes possible provided + f is also a Functor:

+
instance (Functor f) => Functor (NonEmptyList' f) where
+  fmap f (Last' a) = Last' (fmap f a)
+  fmap f (Cons' a ts) = Cons' (f a) (fmap (fmap f) g)
+

And though we needed to modify our syntax and semantics slightly, + the proposed changes do not lose us out on anything of real + consequence:

+
twoPlusThree :: NonEmptyList' (Reader Int) Int
+twoPlusThree = Cons'
+  2 (reader (\a -> Cons'
+    3 (reader (\b -> Last' (a + b)))))
+
+runNonEmptyList' :: NonEmptyList' (Reader Int) Int -> Int
+runNonEmptyList' (Last' a) = a
+runNonEmptyList' (Cons' a f) = runNonEmptyList' (runReader f a)
+
+-- >>> runNonEmptyList' twoPlusThree
+-- 5
+

Compare the above snippet with onePlusTwo'.

+
+

The Applicative instance is slightly more involved so + we’ll hold off on that front for the time-being. For the sake of + forging ahead though, assume it exists. With the Functor + instance established and the Applicative instance + assumed, we are ready to tackle writing the Monad + instance. A first attempt would probably look like the following:

+
instance (Functor f) => Monad (NonEmptyList' f) where
+  (>>=) :: NonEmptyList' f a -> (a -> NonEmptyList' f b) -> NonEmptyList' f b
+  Last' a   >>= g = g a
+  Cons' a f >>= g = Cons' _ (fmap (>>= g) f)
+

Defining bind (>>=) on Last' is + straightforward, but Cons' again presents a problem. With + g serving as our only recourse of converting an + a into anything, how should we fill in the hole + (_)? One approach could be:

+
instance (Functor f) => Monad (NonEmptyList' f) where
+  Cons' a f >>= g =
+    let ts = fmap (>>= g) f
+     in case g a of
+          Last' b    -> Cons' b ts
+          Cons' b f' -> Cons' b (f' <> ts)
+

but this is pretty unsatisfactory. This definition requires a + Semigroup constraint on f, which in turn + requires some lifting operator. After all, how else could we append + Last a1 <> Last a2 together? Suddenly the list of + constraints on f is growing despite our best intentions. + Let’s take a step back and see if there is something else we can + try.

+

The insight falls from the one constraint we had already added + (admittedly without much fanfare). That is, we are requiring type + variable f to be a Functor! With this in + mind, we can actually massage our first parameter into a + bind-compatible one by simply omitting it altogether.

+

To elaborate, it is well + known simple algebraic data types are isomorphic to “primitive” + functors (Identity and Const) and that + (co)products of functors yield more functors. We can therefore + “absorb” the syntax of a into f by + using a product type as a container of sorts:

+
data NonEmptyList'' f a = Last'' a | Cons'' (f (NonEmptyList'' f a))
+
+data Container a m k = Container a (m k) deriving Functor
+
+threePlusFour :: NonEmptyList'' (Container Int (Reader Int)) Int
+threePlusFour = Cons''
+  (Container 3 (reader (\a -> Cons''
+    (Container 4 (reader (\b -> Last'' (a + b)))))))
+
+runNonEmptyList'' :: NonEmptyList'' (Container Int (Reader Int)) Int -> Int
+runNonEmptyList'' (Last'' a) = a
+runNonEmptyList'' (Cons'' (Container a f)) = runNonEmptyList'' (runReader f a)
+
+-- >>> runNonEmptyList'' threePlusFour
+-- 7
+

The above demonstrates NonEmptyList' was in fact + overly specific for our purposes. By generalizing further still, we + lose no expressivity and gain the capacity to finally write our + Monad instance:

+
instance (Functor f) => Monad (NonEmptyList'' f) where
+  Last'' a >>= g = g a
+  Cons'' f >>= g = Cons'' (fmap (>>= g) f)
+

Making an + Applicative

+

The NonEmptyList'' variant actually has another well + known name within the community:

+
data Free f a = Pure a | Free (f (Free f a))
+

We favor this name over NonEmptyList'' from here on + out. In the last section we deferred writing the + Applicative instance for Free but we can now + present its implementation. First, let’s gather some intuition around + how we expect it to work by monomorphizing Free over + Maybe and Int:

+
>>> a = Free (Just (Free (Just (Pure (+1)))))
+>>> b = Pure 5
+>>> c = Free (Just (Pure 5))
+

What should the result of a <*> b be? An + argument could probably be made for either:

+
    +
  1. Free (Just (Free (Just (Pure 6))))
  2. +
  3. Pure 6
  4. +
+

What about for a <*> c? In this case, any one of + the three answers is a potentially valid possibility:

+
    +
  1. Free (Just (Free (Just (Free (Just (Pure 6))))))
  2. +
  3. Free (Just (Free (Just (Pure 6))))
  4. +
  5. Free (Just (Pure 6))
  6. +
+

This ambiguity is why we waited until we finished defining the + Monad instance. Instead of trying to reason about which + instance makes sense, we choose the interpretation that aligns with + our monad.

+
ap :: forall f a b. Functor f => Free f (a -> b) -> Free f a -> Free f b
+ap f g = do
+  f' <- f
+  g' <- g
+  pure (f' g')
+

Examining the results of ap a b and + ap a c, we determine the first entries of the above two + lists must be the answer. Thus it is consistent to define our + Applicative like so:

+
instance (Functor f) => Applicative (Free f) where
+  pure = Pure
+
+  Pure f <*> g = fmap f g
+  Free f <*> g = Free (fmap (<*> g) f)
+

Algebraic Data + Types

+

Let’s revisit our original questions:

+
+
    +
  1. How can we go about converting a simple algebraic data type into a + monad?
  2. +
  3. Does there exist some set of minimal requirements the data type + must fulfill to make this conversion “free”?
  4. +
+
+

We have shown that a data type must be a Functor for + us to build up a Free monad. Additionally, as mentioned earlier, simple algebraic data + types are already functors, thereby answering both questions. + To drive this point home, consider the canonical Teletype + example:

+
data Teletype k = Read k | Write String k deriving Functor
+

Armed with this data type, we can generate programs using the + Teletype DSL. For instance,

+
read :: Free Teletype String
+read = Free (Read (Pure "hello"))
+
+write :: String -> Free Teletype ()
+write s = Free (Write s (Pure ()))
+
+readThenWrite :: Free Teletype ()
+readThenWrite = do
+  input <- read
+  write input
+

Smart constructors read and write are + included to abstract away the boilerplate and help highlight + readThenWrite’s role of syntax. Invoking this function + does not actually do anything, but reading the function makes + it very obvious what we at least want it to do. A + corresponding handler provides the missing semantics:

+
runReadThenWrite :: Free Teletype () -> IO ()
+runReadThenWrite (Free (Write s f)) = putStrLn s >> runReadThenWrite f
+runReadThenWrite (Free (Read f)) = runReadThenWrite f
+runReadThenWrite (Pure _) = pure ()
+

Composing Effects

+

Though neither impressive nor particularly flexible, + readThenWrite is an example of a DSL corresponding to our + Teletype effect. This is only half the + battle though. As mentioned at the start, we want to be able to + compose effects together within the same program. After all, a program + with just one effect doesn’t actually end up buying us much except a + lot of unnecessary abstraction.

+

As we begin our journey down this road, let’s depart from + Teletype and meet up with hopefully a familiar + friend:

+
data State s k = Get (s -> k) | Put s k
+  deriving Functor
+

In the above snippet, State has been rewritten from + our usual MTL-style to a pseudo continuation-passing style compatible + with Free. An example handler might look like:

+
runState :: forall s a. s -> Free (State s) a -> (s, a)
+runState s (Free (Get f)) = runState s (f s)
+runState _ (Free (Put s' f)) = runState s' f
+runState _ (Pure a) = a
+

We can then run this handler on a sample program like so:

+
increment :: Free (State Int) ()
+increment = Free (Get (\s -> Free (Put (s + 1) (Pure ()))))
+
+-- >>> runState 0 increment
+-- (1, ())
+

Let’s raise the ante a bit. Suppose now we wanted to pass around a + second state, e.g. a String. How might we go about doing + this? Though we could certainly rewrite increment to have + state (Int, String) instead of Int, this + feels reminiscient to the expression + problem. Having to update and recompile every one of our programs + every time we introduce some new effect is a maintenance burden we + should not settle on shouldering. Instead, we should aim to write all + of our programs in a way that doesn’t require modifying source.

+

Sum Types

+

Let’s consider what it would take to compose effects + State Int and State String together. In the + world of data types, we usually employ either products or coproducts + to bridge two disjoint types together. Let’s try the latter and see + where we end up:

+
data (f :+: g) k = L (f k) | R (g k)
+  deriving (Functor, Show)
+
+infixr 4 :+:
+

This allows us to join types in the following manner:

+
>>> L (Just 5) :: (Maybe :+: Identity) Int
+L (Just 5)
+>>> R (Identity 5) :: (Maybe :+: Identity) Int
+R (Identity 5)
+

We call this chain of functors a signature. We can + compose a signature containing our Int and + String state as well as a handler capable of interpreting + it:

+
runTwoState
+  :: forall s1 s2 a
+   . s1
+  -> s2
+  -> Free (State s1 :+: State s2) a
+  -> (s1, s2, a)
+runTwoState s1 s2 (Free (L (Get f)))    = runTwoState s1 s2 (f s1)
+runTwoState s1 s2 (Free (R (Get f)))    = runTwoState s1 s2 (f s2)
+runTwoState _  s2 (Free (L (Put s1 f))) = runTwoState s1 s2 f
+runTwoState s1 _  (Free (R (Put s2 f))) = runTwoState s1 s2 f
+runTwoState s1 s2 (Pure a)              = (s1, s2, a)
+

It’s functional but hardly a solution. It requires manually writing + every combination of effects introduced by :+: - a + straight up herculean task as the signature gets longer. It also does + not address the “expression problem”. That said, it does + provide the scaffolding for a more polymorphic solution. We can bypass + this combinatorial explosion of patterns by focusing on just one + effect at a time, parameterizing the remainder of the signature. + Handlers can then “peel” an effect off a signature, over and over, + until we are out of effects to peel:

+
runState' ::
+  forall s a sig.
+  Functor sig =>
+  s ->
+  Free (State s :+: sig) a ->
+  Free sig (s, a)
+runState' s (Pure a) = pure (s, a)
+runState' s (Free (L (Get f))) = runState' s (f s)
+runState' _ (Free (L (Put s f))) = runState' s f
+runState' s (Free (R other)) = Free (fmap (runState' s) other)
+

The above function combines the ideas of runState and + runTwoState into a more general interface. Now programs + containing State effects in any order can be interpreted + by properly ordering the handlers:

+
threadedState :: Free (State Int :+: State String) ()
+threadedState =
+  Free (L (Get (\s1 ->
+    Free (R (Get (\s2 ->
+      Free (L (Put (s1 + 1)
+        (Free (R (Put (s2 ++ "a")
+          (Pure ()))))))))))))
+
+threadedState' :: Free (State String :+: State Int) ()
+threadedState' = ...
+
+-- >>> runState "" . runState' @Int 0 $ threadedState
+-- ("a",(1,()))
+-- >>> runState @Int 0 . runState' "" $ threadedState'
+-- (1,("a",()))
+

Membership

+

We can do better still. Our programs are far too concerned with the + ordering of their corresponding signatures. The only thing they should + care about is whether the effect exists at all. We can relax this + coupling by introducing a new recursive typeclass:

+
class Member sub sup where
+  inj :: sub a -> sup a
+  prj :: sup a -> Maybe (sub a)
+

Here sub is said to be a subtype of + sup. inj allows us to promote that subtype + to sup and prj allows us to dynamically + downcast back to sub. This typeclass synergizes + especially well with :+:. For instance, we expect + State Int to be a subtype of + State Int :+: State String. Importantly, we’d expect the + same for State String. Let’s consider how instances of + Member might look. First is reflexivity:

+
instance Member sig sig where
+  inj = id
+  prj = Just
+

This instance should be fairly straightforward. We want to be able + to cast a type to and from itself without issue. Next is + left-occurrence:

+
instance Member sig (sig :+: r) where
+  inj       = L
+  prj (L f) = Just f
+  prj _     = Nothing
+

This is the pattern we’ve been working with up until now. Casting + upwards is just a matter of using the L data constructor + while projecting back down works so long as we are within the + L context. Likewise there exists a right-recursion + rule:

+
instance (Member sig r) => Member sig (l :+: r) where
+  inj       = R . inj
+  prj (R g) = prj g
+  prj _     = Nothing
+

Lastly, as a convenience, we introduce left-recursion:

+
instance Member sig (l1 :+: (l2 :+: r)) =>
+         Member sig ((l1 :+: l2) :+: r) where
+  inj sub = case inj sub of
+    L l1     -> L (L l1)
+    R (L l2) -> L (R l2)
+    R (R r)  -> R r
+  prj sup = case sup of
+    L (L l1) -> prj (L l1)
+    L (R l2) -> prj (R (L l2))
+    R r      -> prj (R (R r))
+

The above allows us to operate on a tree of types rather + than a list. We can read this as saying “subtying is not affected by + how :+: is associated.”

+ <.warning> +

These instances will not compile as is. A mix of + TypeApplications and OVERLAPPING pragmas + must be used. Refer to the git repository + for the real implementation.

+ +

With the above instances in place, we can now create a more + flexible implementation of threadedState above:

+
data Void k deriving Functor
+
+run :: forall a. Free Void a -> a
+run (Pure a) = a
+run _ = error (pack "impossible")
+
+threadedState'' ::
+  Functor sig =>
+  Member (State Int) sig =>
+  Member (State String) sig =>
+  Free sig ()
+threadedState'' =
+  Free (inj (Get @Int (\s1 ->
+    Free (inj (Get (\s2 ->
+      Free (inj (Put (s1 + 1)
+        (Free (inj (Put (s2 ++ "a")
+          (Pure ()))))))))))))
+
+-- >>> run . runState' "" . runState' @Int 0 $ threadedState''
+-- ("a",(1,()))
+-- >>> run . runState' @Int 0 . runState' "" $ threadedState''
+-- (1,("a",()))
+

A few takeaways:

+
    +
  1. The program now stays polymorphic in type sig,
  2. +
  3. We no longer explicitly mention L or R + data constructors, and
  4. +
  5. We use run to peel away the final effect.
  6. +
+

This flexibility grants us the ability to choose the order + we handle effects at the call site. By writing a few additional smart + constructors, we could have a nicer program still:

+
inject :: (Member sub sup) => sub (Free sup a) -> Free sup a
+inject = Free . inj
+
+project :: (Member sub sup) => Free sup a -> Maybe (sub (Free sup a))
+project (Free s) = prj s
+project _        = Nothing
+
+get :: Functor sig => Member (State s) sig => Free sig s
+get = inject (Get pure)
+
+put :: Functor sig => Member (State s) sig => s -> Free sig ()
+put s = inject (Put s (pure ()))
+
+threadedStateM'' ::
+  Functor sig =>
+  Member (State Int) sig =>
+  Member (State String) sig =>
+  Free sig ()
+threadedStateM'' = do
+  s1 <- get @Int
+  s2 <- get @String
+  put (s1 + 1)
+  put (s2 ++ "a")
+  pure ()
+

Higher-Order Effects

+

This composition provides many benefits, but in certain situations + we end up hitting a wall. To continue forward, we borrow an example + from Effect Handlers in + Scope. In particular, we discuss exception handling and how we can + use a free monad to simulate throwing and catching exceptions.

+
newtype Throw e k = Throw e deriving (Functor)
+
+throw e = inject (Throw e)
+ <.info> +

To avoid too many distractions, we will sometimes skip writing type + signatures.

+ +

This Throw type should feel very intuitive at this + point. We take an exception and “inject” it into our program using the + throw smart constructor. What’s the + catch?

+
catch (Pure a)             _ = pure a
+catch (Free (L (Throw e))) h = h e
+catch (Free (R other))     h = Free (fmap (`catch` h) other)
+

In this scenario, catch traverses our program, happily + passing values through until it encounters a Throw. Our + respective “peel” looks like so:

+
runThrow :: forall e a sig. Free (Throw e :+: sig) a -> Free sig (Either e a)
+runThrow (Pure a) = pure (Right a)
+runThrow (Free (L (Throw e))) = pure (Left e)
+runThrow (Free (R other)) = Free (fmap runThrow other)
+

We now have the requisite tools needed to build up and execute a + sample program that composes some State Int effect with a + Throw effect:

+
countDown ::
+  forall sig.
+  Functor sig =>
+  Member (State Int) sig =>
+  Member (Throw ()) sig =>
+  Free sig ()
+countDown = do
+  decr {- 1 -}
+  catch (decr {- 2 -} >> decr {- 3 -}) pure
+ where
+  decr = do
+    x <- get @Int
+    if x > 0 then put (x - 1) else throw ()
+

This program calls a potentially dangerous decr + function three times, with the last two attempts wrapped around a + catch.

+

Scoping + Problems

+

How should the state of countDown be interpreted? + There exist two reasonable options:

+
    +
  1. If state is considered global, then successful + decrements in catch should persist. That is, our final state would be + the initial value decremented as many times as decr + succeeds.
  2. +
  3. If state is considered local, we expect + catch to decrement state twice but to rollback + if an error is raised. If an error is caught, our final state would be + the initial value decremented just the once.
  4. +
+

This is what it means for an operation to be + scoped. In the local case, within the semantics of + exception handling, the nested program within catch + should not affect the state of the world outside of it in the case of + an exception. Let’s see if we can somehow write and invoke handlers + accordingly:

+
>>> run . runThrow @() . runState' @Int 3 $ countDown
+Right (0,())
+

The above snippet demonstrates a result we expect in either + interpretation. The nested decr >> decr raises no + error. Likewise

+
>>> run . runThrow @() . runState' @Int 0 $ countDown
+Left ()
+

should also feel correct, regardless of interpretation. + decr {- 1 -} ends up returning a throw () + which the subsequent runThrow handler interprets as + Left. What about the following?

+
>>> run . runThrow @() . runState' @Int 2 $ countDown
+Right (0,())
+

This is an example of a global interpretation. Here we throw an + error on decr {- 3 -} but decr {- 2 -}’s + effects persist despite existing within the catch. So can + we scope the operation? As it turns out, local semantics are out of + reach. “Flattening” the program hopefully makes the reason + clearer:

+
countDown' =
+  Free (inj (Get @Int (\x ->
+    let a = \k -> if x > 0 then Free (inj (Put (x - 1) k)) else throw ()
+     in a (catch (Free (inj (Get @Int (\y ->
+      let b = \k -> if y > 0 then Free (inj (Put (y - 1) k)) else throw ()
+       in b (Free (inj (Get @Int (\z ->
+         let c = \k -> if z > 0 then Free (inj (Put (z - 1) k)) else throw ()
+          in c (Pure ()))))))))) pure))))
+

It’s noisy, but in the above snippet we see there exists no + mechanism that “saves” the state prior to running the nested + program.

+

A Stronger Free

+

Somehow we need to ensure a nested (e.g. the program scoped within + catch) does not “leak” in any way. To support programs + within programs (within programs within programs…) within the already + recursively defined free monad, we look towards a higher-level + abstraction for help. According to Wu, Schrijvers, and Hinze,

+
+

A more direct solution [to handle some self-contained context] is + to model scoping constructs with higher-order syntax, where the syntax + carries those syntax blocks directly.

+
+

What would such a change look like? To answer that, it proves + illustrative understanding why our current definition of + Free is insufficient. Consider what it means to “run” our + program. We have a handler that traverses the program, operates on + effects it knows how to operate on, and then returns a slightly less + abstract program for the next handler to process. To save state, we + somehow need each handler to refer to a context + containing state as defined by the handler prior.

+

As a starting point, review our current definition of + Free:

+
data Free f a = Pure a | Free (f (Free f a))
+

We see a is not something we, the effects library + author, are in a position to manipulate. To actually extract a value + to be saved and threaded in a context though, we at the very least + need this ability. So can we introduce some change that give us this + freedom? One idea is:

+
data Free f a = Pure a | Free (f (Free f) a)
+

The change is subtle but has potential provided we can get all the + derived type machinery working on this type instead. Take note! + Previously the kind of f was + Type -> Type. In this new definition, we see it is now + (Type -> Type) -> (Type -> Type). That is, + f is now a function that maps one type function to + another. We have entered the world of higher-order kinds.

+ <.info> +

f is usually a natural transformation, mapping one + functor to another. The specifics regarding natural transformations + aren’t too important here. Just note when we use the term going + forward, we mean a functor to functor mapping.

+ +

Ideally we can extrapolate our learnings so far to this + higher-order world. Of most importance is our + Functor-constrained type variable f. Let’s + dive a bit deeper into what it currently buys us. First, take another + look at how fmap is used within Free’s + Monad instance:

+
instance (Functor f) => Monad (Free f) where
+  Pure a >>= g = g a
+  Free f >>= g = Free (fmap (>>= g) f)
+

Its purpose is to allow extending our syntax, chaining + different DSL terms together into a bigger program. When we write + e.g.

+
readThenWrite = do
+  input <- read
+  write input
+

it is fmap that is responsible for piecing the + read and write together. Second, re-examine + a sample handler, e.g.

+
runState' s (Pure a)             = pure (s, a)
+runState' s (Free (L (Get f)))   = runState' s (f s)
+runState' _ (Free (L (Put s f))) = runState' s f
+runState' s (Free (R other))     = Free (fmap (runState' s) other)
+

In this case, fmap is responsible for weaving + the state semantics throughout the syntax. This is what allows us to + interpret programs comprised of multiple different syntaxes. Whatever + we end up building at the higher level needs to keep both these + aspects in mind.

+

Higher-Order + Syntax

+

Syntax is the easier of the two to resolve so that’s where we’ll + first avert out attention. Extension requires two things:

+
    +
  1. A higher-level concept of a functor to constrain our new + f, and
  2. +
  3. An fmap-like function capable of performing the + extension.
  4. +
+

Building out (1) is fairly straightforward. Since f + corresponds to a natural transformation, we create a mapping between + functors like so:

+
class HFunctor f where
+  hmap ::
+    (Functor m, Functor n) =>
+    (forall x. m x -> n x) ->
+    (forall x. f m x -> f n x)
+

This allows us to lift transformations of + e.g. Identity -> Maybe into + f Identity -> f Maybe. Take a moment to notice the + parallels between fmap and hmap. Building + (2) is equally simple:

+
class HFunctor f => Syntax f where
+  emap :: (m a -> m b) -> (f m a -> f m b)
+

We designate emap as our fmap-extending + equivalent. This is made obvious by seeing how Free ends + up using emap:

+
instance Syntax f => Monad (Free f) where
+  Pure a >>= g = g a
+  Free f >>= g = Free (emap (>>= g) f)
+

Once again, note the parallels betwen the Monad + instances of both Frees.

+

Higher-Order + Semantics

+

The more difficult problem lies on the semantic side of the + equation. This part needs to manage the threading of functions + throughout potentially nested effects. To demonstrate, consider a + revision to our Throw type that includes a + Catch at the syntactic level:

+
data Error e m a = Throw e
+                 | forall x. Catch (m x) (e -> m x) (x -> m a)
+

We can create Error instances of our + HFunctor and Syntax classes as follows:

+
instance HFunctor (Error e) where
+  hmap _ (Throw x) = Throw x
+  hmap t (Catch p h k) = Catch (t p) (t . h) (t . k)
+
+instance Syntax (Error e) where
+  emap _ (Throw e) = Throw e
+  emap f (Catch p h k) = Catch p h (f . k)
+

This is all well and good, but now suppose we want to write a + handler in the same way we wrote runThrow earlier:

+
runError ::
+  forall e a sig.
+  Syntax sig =>
+  Free (Error e :+: sig) a ->
+  Free sig (Either e a)
+runError (Pure a)                 = pure (Right a)
+runError (Free (L (Throw e)))     = pure (Left e)
+runError (Free (L (Catch p h k))) =
+  runError p >>= \case
+    Left e ->
+      runError (h e) >>= \case
+        Left e' -> pure (Left e')
+        Right a -> runError (k a)
+    Right a -> runError (k a)
+runError (Free (R other))         = Free _
+

Make sure everything leading up to the last pattern makes sense and + then ask yourself how you might fill in the hole (_). We + only have a few tools at our disposal, namely hmap and + emap. But, no matter how we choose to compose them, + hmap will let us down. In particular, our only means of + “peeling” the signature is runError which is incompatible + with the natural transformation hmap expects.

+
+

We need another function specific for this weaving behavior, which + we choose to add to the Syntax typeclass:

+
class HFunctor f => Syntax f where
+  emap :: (m a -> m b) -> (f m a -> f m b)
+
+  weave ::
+    (Monad m, Monad n, Functor ctx) =>
+    ctx () ->
+    Handler ctx m n ->
+    (f m a -> f n (ctx a))
+
+type Handler ctx m n = forall x. ctx (m x) -> n (ctx x)
+

Pay special attention to Handler. By introducing a + functorial context (i.e. ctx), we have defined a function + signature that more closely reflects that of runError. + This is made clearer by instantiating ctx to + Either e:

+
type Handler m n = forall x. Either e (m x) -> n (Either e x)
+
+runError :: Free (Error e :+: sig) a -> Free sig (Either e a)
+

Without ctx, weave would look just like + hmap, highlighting how it’s particularly well-suited to + bypassing the hmap’s limitations. With weave + also comes expanded Syntax instances:

+
instance (Syntax f, Syntax g) => Syntax (f :+: g) where
+  weave ctx hdl (L f) = L (weave ctx hdl f)
+  weave ctx hdl (R g) = R (weave ctx hdl g)
+
+instance Syntax (Error e) where
+  weave _ _ (Throw x) = Throw x
+  weave ctx hdl (Catch p h k) =
+    -- forall x. Catch (m x) (e -> m x) (x -> m a)
+    Catch
+      (hdl (fmap (const p) ctx))
+      (\e -> hdl (fmap (const (h e)) ctx))
+      (hdl . fmap k)
+

const is used solely to wrap our results in a way that + hdl expects. With these instances fully defined, we can + now finish our runError handler:

+
runError (Free (R other)) =
+  Free $ weave (Right ()) (either (pure . Left) runError) other
+

Lifting

+

The solution we’ve developed so far wouldn’t be especially useful + if it was weaker than the previous. As mentioned before, our new + solution splits the functionality of fmap into extension + and weaving. But nothing is stopping us from defining an instance that + continues using fmap for both. Consider the + following:

+
newtype Lift sig (m :: Type -> Type) a = Lift (sig (m a))
+

Here sig refers to the lower-order data type we want + to elevate to our higher-order Free, + e.g. State s:

+
type HState s = Lift (State s)
+
+hIncrement :: Free (Lift (State Int)) ()
+hIncrement = Free (Lift (Get (\s -> Free (Lift (Put (s + 1) (Pure ()))))))
+
+type HVoid = Lift Void
+
+run :: Free HVoid a -> a
+run (Pure a) = a
+run _ = error (pack "impossible")
+

Here hIncrement is a lifted version of + increment defined before. Likewise, run + remains nearly identical to its previous definition. Making + Lift an instance of Syntax is a equally + straightforward:

+
 instance Functor sig => HFunctor (Lift sig) where
+   hmap t (Lift f) = Lift (fmap t f)
+
+ instance Functor sig => Syntax (Lift sig) where
+   emap t (Lift f) = Lift (fmap t f)
+
+   weave ctx hdl (Lift f) = Lift (fmap (\p -> hdl (fmap (const p) ctx)) f)
+

The corresponding smart constructors and state handler should look + like before, but with our ctx now carrying the state + around:

+
get :: forall s sig. HFunctor sig => Member (HState s) sig => Free sig s
+get = inject (Lift (Get Pure))
+
+put :: forall s sig. HFunctor sig => Member (HState s) sig => s -> Free sig ()
+put s = inject (Lift (Put s (pure ())))
+
+runState ::
+  forall s a sig.
+  Syntax sig =>
+  s ->
+  Free (HState s :+: sig) a ->
+  Free sig (s, a)
+runState s (Pure a) = pure (s, a)
+runState s (Free (L (Lift (Get f)))) = runState s (f s)
+runState _ (Free (L (Lift (Put s f)))) = runState s f
+runState s (Free (R other)) = Free (weave (s, ()) hdl other)
+  where
+    hdl :: forall x. (s, Free (HState s :+: sig) x) -> Free sig (s, x)
+    hdl = uncurry runState
+

With all this in place, we can finally construct our + countDown example again:

+
countDown ::
+  forall sig.
+  Syntax sig =>
+  Member (HState Int) sig =>
+  Member (Error ()) sig =>
+  Free sig ()
+countDown = do
+  decr {- 1 -}
+  catch (decr {- 2 -} >> decr {- 3 -}) pure
+  where
+    decr = do
+      x <- get @Int
+      if x > 0 then put (x - 1) else throw ()
+

Now if we encounter an error within our catch + statement, the local state semantics are respected:

+
>>> run . runError @() . runState @Int 2 $ countDown
+Right (1,())
+

Pay attention to why this works - we first use our + runState handler and eventually encounter + decr {- 3 -} which returns throw () instead + of put (x - 1). During this process, weave was invoked on + a Catch with context (s, ) used to maintain + the state at the time. Next runError is invoked which + sees the Catch, encounters the returned + Throw after running the scoped program, and invokes the + error handler which has our saved state.

+

Limitations

+

Though the higher-order free implementation is largely a useful + tool for managing effects, it is not perfect. I’ve had an especially + hard time getting resource-oriented effects working, e.g. with custom + effects like so:

+
data Server hdl conn (m :: * -> *) k where
+  Start :: SpawnOptions -> Server hdl conn m hdl
+  Stop :: hdl -> Server hdl conn m ExitCode
+  GetPort :: hdl -> Server hdl conn m PortNumber
+  Open :: Text -> PortNumber -> Server hdl conn m conn
+  Close :: conn -> Server hdl conn m ()
+

The issue here being running the custom Server handler + invokes start and stop when wrapped + in some bracket-like + interface, even if the bracketed code has not yet finished. I have + settled on workarounds, but these workarounds consist of just + structuring these kind of effects differently.

+

In general, modeling asynchronous or IO-oriented + operations feel “unsolved” with solutions resorting to some forklift + strategy or other ad-hoc solutions that don’t feel as cemented in + literature. I don’t necessarily think these are the wrong + approach (I frankly don’t know enough to have a real opinion here), + but it’d be nice to feel there was some consensus as to what a + non-hacky solution theoretically looks like.

+

Additionally, it is cumbersome remembering the order handlers + should be applied to achieve the desired global vs. local state + semantics. This is not exclusively a problem of free effect systems + (e.g. MTL also suffers from this), but the issue feels more prominent + here.

+

Conclusion

+

I will continue exploring effect systems à la free, but I am + admittedly not yet convinced they are the right way forward. + Unfortunately, they can be hard to reason about with unexpected + interactions between effects if not careful. I am sure a large + contributing factor to this conclusion is the lack of + beginner-oriented documentation regarding proper use and edge cases. + Just to build up this post required reading source code of multiple + effects libraries and scattered blog posts, watching various YouTube + videos, etc. And, despite all that, I am still not confident I + understand the implementation details behind certain key abstractions. + Hopefully this entry threads the needle between exposition and overt + jargon to get us a little closer though.

+ +
diff --git a/lib/portfolio_web/controllers/blog_html/effect_systems.md b/lib/portfolio_web/controllers/blog_html/effect_systems.md new file mode 100644 index 0000000..a5e7ce9 --- /dev/null +++ b/lib/portfolio_web/controllers/blog_html/effect_systems.md @@ -0,0 +1,1088 @@ +--- +title: Effect Systems +date: 20 Mar 2022 +abstract: | + As I've begun exploring the world of so-called **algebraic effect systems**, + I've become increasingly frustrated in the level of documentation around + them. Learning to use them (and moreso understanding how they work) requires + diving into library internals, watching various videos, and hoping to grok + why certain effects aren't being interpreted the way you might have hoped. My + goal in this post is to address this issue, at least to some degree, in a + focused, pedagogical fashion. +

+ A large portion of this post has been derived from the implementation of the + [fused-effects](https://www.github.com/fused-effects/fused-effects) library, + chosen because it seems to have the most active development, the smallest + dependency footprint, and minimal type machinery. In turn, this library was + largely inspired by Nicolas Wu, Tom Schrijvers, and Ralf Hinze's work in + [Effect Handlers in Scope](/assets/pdf/effect-systems/scope.pdf). As such, + we'll discuss choice parts of this paper as well. +

+ Code snippets can be found in + [this git repository](https://git.jrpotter.com/blog/effect-systems). +--- + +## Free Monads + +To begin our exploration, let's pose a few questions: + +1. How can we go about converting a simple algebraic data type into a monad? +1. Does there exist some set of minimal requirements the data type must fulfill +to make this conversion "free"?[^1] + +To help guide our decision making, we'll examine the internals of some arbitrary +monad. More concretely, let's see what `1 + 2` could look like within a monadic +context: + +```haskell +onePlusTwo :: forall m. Monad m => m Int +onePlusTwo = do + a <- pure 1 + b <- pure 2 + pure $ a + b +``` + +The above won't win any awards, but it should be illustrative enough for our +purposes. `do` is just syntactic sugar for repeated bind applications (`>>=`), +so we could've written the above alternatively as: + +```haskell +onePlusTwo' :: forall m. Monad m => m Int +onePlusTwo' = pure 1 >>= (\a -> pure 2 >>= (\b -> pure (a + b))) +``` + +This is where we'll pause for a moment and squint. We see that `do` syntax +desugars into something that looks awfully close to a (non-empty) list! Let's +compare the above with how we might define that: + +```haskell +data NonEmptyList a = Last a | Cons a (a -> NonEmptyList a) + +onePlusTwo'' :: NonEmptyList Int +onePlusTwo'' = Cons 1 (\a -> Cons 2 (\b -> Last (a + b))) + +runNonEmptyList :: NonEmptyList Int -> Int +runNonEmptyList (Last a) = a +runNonEmptyList (Cons a f) = runNonEmptyList (f a) + +-- >>> runIdentity onePlusTwo' +-- 3 +-- >>> runNonEmptyList onePlusTwo'' +-- 3 +``` + +Take a moment to appreciate the rough pseudo-equivalence of `NonEmptyList` and a +monad. Also take a moment to appreciate the differences. Because we no longer +employ the bind operator anywhere within our function definitions, we have +effectively separated the **syntax** of our original function from its +**semantics**. That is, `onePlusTwo''` can be viewed as a *program* in and of +itself, and the `runNonEmptyList` **handler** can be viewed as the +interpretation of said program. + +### Making a Monad + +`NonEmptyList` was formulated from a monad, so it's natural to think perhaps it +too is a monad. Unfortunately this is not the case - it isn't even a functor! +Give it a try or use `{-# LANGUAGE DeriveFunctor #-}` to ask the compiler to +make an attempt on your behalf. + +```{=html} +<.tip> +``` +Type variable `a` is said to be contravariant with respect to `Cons`. That is, +`a` resides in a [negative position](https://www.fpcomplete.com/blog/covariance-contravariance/) +within `Cons`'s function. +```{=html} + +``` + +If we can't make this a monad as is, are there a minimal number of changes we +could introduce to make it happen? Ideally our changes maintain the "shape" of +the data type as much as possible, thereby maintaining the integrity behind the +original derivation. Since our current roadblock stems from type variable `a`'s +position in `Cons`, let's see what happens if we just abstract it away: + +```haskell +data NonEmptyList' f a = Last' a | Cons' a (f (NonEmptyList' f a)) +``` + +With general parameter `f` now in the place of `(->) a`, a functor derivation +becomes possible provided `f` is also a `Functor`: + +```haskell +instance (Functor f) => Functor (NonEmptyList' f) where + fmap f (Last' a) = Last' (fmap f a) + fmap f (Cons' a ts) = Cons' (f a) (fmap (fmap f) g) +``` + +And though we needed to modify our syntax and semantics slightly, the proposed +changes do not lose us out on anything of real consequence: + +```haskell +twoPlusThree :: NonEmptyList' (Reader Int) Int +twoPlusThree = Cons' + 2 (reader (\a -> Cons' + 3 (reader (\b -> Last' (a + b))))) + +runNonEmptyList' :: NonEmptyList' (Reader Int) Int -> Int +runNonEmptyList' (Last' a) = a +runNonEmptyList' (Cons' a f) = runNonEmptyList' (runReader f a) + +-- >>> runNonEmptyList' twoPlusThree +-- 5 +``` + +Compare the above snippet with `onePlusTwo'`. + +--- + +The `Applicative` instance is slightly more involved so we'll hold off on that +front for the time-being. For the sake of forging ahead though, assume it +exists. With the `Functor` instance established and the `Applicative` instance +assumed, we are ready to tackle writing the `Monad` instance. A first attempt +would probably look like the following: + +```haskell +instance (Functor f) => Monad (NonEmptyList' f) where + (>>=) :: NonEmptyList' f a -> (a -> NonEmptyList' f b) -> NonEmptyList' f b + Last' a >>= g = g a + Cons' a f >>= g = Cons' _ (fmap (>>= g) f) +``` + +Defining bind (`>>=`) on `Last'` is straightforward, but `Cons'` again presents +a problem. With `g` serving as our only recourse of converting an `a` into +anything, how should we fill in the hole (`_`)? One approach could be: + +```haskell +instance (Functor f) => Monad (NonEmptyList' f) where + Cons' a f >>= g = + let ts = fmap (>>= g) f + in case g a of + Last' b -> Cons' b ts + Cons' b f' -> Cons' b (f' <> ts) +``` + +but this is pretty unsatisfactory. This definition requires a `Semigroup` +constraint on `f`, which in turn requires some lifting operator. After all, how +else could we append `Last a1 <> Last a2` together? Suddenly the list of +constraints on `f` is growing despite our best intentions. Let's take a step +back and see if there is something else we can try. + +The insight falls from the one constraint we had already added (admittedly +without much fanfare). That is, we are requiring type variable `f` to be a +`Functor`! With this in mind, we can actually massage our first parameter into a +bind-compatible one by simply omitting it altogether. + +To elaborate, it is [well known](https://bartoszmilewski.com/2015/02/03/functoriality/) +simple algebraic data types are isomorphic to "primitive" functors (`Identity` +and `Const`) and that (co)products of functors yield more functors. We can +therefore "absorb" the syntax of `a` *into* `f` by using a product type as a +container of sorts: + +```haskell +data NonEmptyList'' f a = Last'' a | Cons'' (f (NonEmptyList'' f a)) + +data Container a m k = Container a (m k) deriving Functor + +threePlusFour :: NonEmptyList'' (Container Int (Reader Int)) Int +threePlusFour = Cons'' + (Container 3 (reader (\a -> Cons'' + (Container 4 (reader (\b -> Last'' (a + b))))))) + +runNonEmptyList'' :: NonEmptyList'' (Container Int (Reader Int)) Int -> Int +runNonEmptyList'' (Last'' a) = a +runNonEmptyList'' (Cons'' (Container a f)) = runNonEmptyList'' (runReader f a) + +-- >>> runNonEmptyList'' threePlusFour +-- 7 +``` + +The above demonstrates `NonEmptyList'` was in fact overly specific for our +purposes. By generalizing further still, we lose no expressivity and gain the +capacity to finally write our `Monad` instance: + +```haskell +instance (Functor f) => Monad (NonEmptyList'' f) where + Last'' a >>= g = g a + Cons'' f >>= g = Cons'' (fmap (>>= g) f) +``` + +### Making an Applicative + +The `NonEmptyList''` variant actually has another well known name within the +community: + +```haskell +data Free f a = Pure a | Free (f (Free f a)) +``` + +We favor this name over `NonEmptyList''` from here on out. In the last section +we deferred writing the `Applicative` instance for `Free` but we can now present +its implementation. First, let's gather some intuition around how we expect it +to work by monomorphizing `Free` over `Maybe` and `Int`: + +```haskell +>>> a = Free (Just (Free (Just (Pure (+1))))) +>>> b = Pure 5 +>>> c = Free (Just (Pure 5)) +``` + +What should the result of `a <*> b` be? An argument could probably be made for +either: + +1. `Free (Just (Free (Just (Pure 6))))` +1. `Pure 6` + +What about for `a <*> c`? In this case, any one of the three answers is a +potentially valid possibility: + +1. `Free (Just (Free (Just (Free (Just (Pure 6))))))` +1. `Free (Just (Free (Just (Pure 6))))` +1. `Free (Just (Pure 6))` + +This ambiguity is why we waited until we finished defining the `Monad` instance. +Instead of trying to reason about which instance makes sense, we choose the +interpretation that aligns with our monad. + +```haskell +ap :: forall f a b. Functor f => Free f (a -> b) -> Free f a -> Free f b +ap f g = do + f' <- f + g' <- g + pure (f' g') +``` + +Examining the results of `ap a b` and `ap a c`, we determine the first entries +of the above two lists must be the answer. Thus it is consistent to define our +`Applicative` like so: + +```haskell +instance (Functor f) => Applicative (Free f) where + pure = Pure + + Pure f <*> g = fmap f g + Free f <*> g = Free (fmap (<*> g) f) +``` + +### Algebraic Data Types + +Let's revisit our original questions: + +> 1. How can we go about converting a simple algebraic data type into a monad? +> 1. Does there exist some set of minimal requirements the data type must +> fulfill to make this conversion "free"? + +We have shown that a data type must be a `Functor` for us to build up a `Free` +monad. Additionally, as [mentioned earlier](#making-a-monad), simple algebraic +data types are *already* functors, thereby answering both questions. To drive +this point home, consider the canonical `Teletype` example: + +```haskell +data Teletype k = Read k | Write String k deriving Functor +``` + +Armed with this data type, we can generate programs using the `Teletype` DSL. +For instance, + +```haskell +read :: Free Teletype String +read = Free (Read (Pure "hello")) + +write :: String -> Free Teletype () +write s = Free (Write s (Pure ())) + +readThenWrite :: Free Teletype () +readThenWrite = do + input <- read + write input +``` + +Smart constructors `read` and `write` are included to abstract away the +boilerplate and help highlight `readThenWrite`'s role of syntax. Invoking this +function does not actually *do* anything, but reading the function makes it very +obvious what we at least *want* it to do. A corresponding handler provides the +missing semantics: + +```haskell +runReadThenWrite :: Free Teletype () -> IO () +runReadThenWrite (Free (Write s f)) = putStrLn s >> runReadThenWrite f +runReadThenWrite (Free (Read f)) = runReadThenWrite f +runReadThenWrite (Pure _) = pure () +``` + +## Composing Effects + +Though neither impressive nor particularly flexible, `readThenWrite` is an +example of a DSL corresponding to our `Teletype` **effect**. This is only half +the battle though. As mentioned at the start, we want to be able to compose +effects together within the same program. After all, a program with just one +effect doesn't actually end up buying us much except a lot of unnecessary +abstraction. + +As we begin our journey down this road, let's depart from `Teletype` and meet up +with hopefully a familiar friend: + +```haskell +data State s k = Get (s -> k) | Put s k + deriving Functor +``` + +In the above snippet, `State` has been rewritten from our usual MTL-style to a +pseudo continuation-passing style compatible with `Free`. An example handler +might look like: + +```haskell +runState :: forall s a. s -> Free (State s) a -> (s, a) +runState s (Free (Get f)) = runState s (f s) +runState _ (Free (Put s' f)) = runState s' f +runState _ (Pure a) = a +``` + +We can then run this handler on a sample program like so: + +```haskell +increment :: Free (State Int) () +increment = Free (Get (\s -> Free (Put (s + 1) (Pure ())))) + +-- >>> runState 0 increment +-- (1, ()) +``` + +Let's raise the ante a bit. Suppose now we wanted to pass around a second state, +e.g. a `String`. How might we go about doing this? Though we could certainly +rewrite `increment` to have state `(Int, String)` instead of `Int`, this feels +reminiscient to the [expression problem](/posts/tagless-final-parsing#expression-problem). +Having to update and recompile every one of our programs every time we introduce +some new effect is a maintenance burden we should not settle on shouldering. +Instead, we should aim to write all of our programs in a way that doesn't +require modifying source. + +### Sum Types + +Let's consider what it would take to compose effects `State Int` and +`State String` together. In the world of data types, we usually employ either +products or coproducts to bridge two disjoint types together. Let's try the +latter and see where we end up: + +```haskell +data (f :+: g) k = L (f k) | R (g k) + deriving (Functor, Show) + +infixr 4 :+: +``` + +This allows us to join types in the following manner: + +```haskell +>>> L (Just 5) :: (Maybe :+: Identity) Int +L (Just 5) +>>> R (Identity 5) :: (Maybe :+: Identity) Int +R (Identity 5) +``` + +We call this chain of functors a **signature**. We can compose a signature +containing our `Int` and `String` state as well as a handler capable of +interpreting it: + +```haskell +runTwoState + :: forall s1 s2 a + . s1 + -> s2 + -> Free (State s1 :+: State s2) a + -> (s1, s2, a) +runTwoState s1 s2 (Free (L (Get f))) = runTwoState s1 s2 (f s1) +runTwoState s1 s2 (Free (R (Get f))) = runTwoState s1 s2 (f s2) +runTwoState _ s2 (Free (L (Put s1 f))) = runTwoState s1 s2 f +runTwoState s1 _ (Free (R (Put s2 f))) = runTwoState s1 s2 f +runTwoState s1 s2 (Pure a) = (s1, s2, a) +``` + +It's functional but hardly a solution. It requires manually writing every +combination of effects introduced by `:+:` - a straight up herculean task as the +signature gets longer. It also does not address the "expression problem". That +said, it *does* provide the scaffolding for a more polymorphic solution. We can +bypass this combinatorial explosion of patterns by focusing on just one effect +at a time, parameterizing the remainder of the signature. Handlers can then +"peel" an effect off a signature, over and over, until we are out of effects to +peel: + +```haskell +runState' :: + forall s a sig. + Functor sig => + s -> + Free (State s :+: sig) a -> + Free sig (s, a) +runState' s (Pure a) = pure (s, a) +runState' s (Free (L (Get f))) = runState' s (f s) +runState' _ (Free (L (Put s f))) = runState' s f +runState' s (Free (R other)) = Free (fmap (runState' s) other) +``` + +The above function combines the ideas of `runState` and `runTwoState` into a +more general interface. Now programs containing `State` effects in any order can +be interpreted by properly ordering the handlers: + +```haskell +threadedState :: Free (State Int :+: State String) () +threadedState = + Free (L (Get (\s1 -> + Free (R (Get (\s2 -> + Free (L (Put (s1 + 1) + (Free (R (Put (s2 ++ "a") + (Pure ())))))))))))) + +threadedState' :: Free (State String :+: State Int) () +threadedState' = ... + +-- >>> runState "" . runState' @Int 0 $ threadedState +-- ("a",(1,())) +-- >>> runState @Int 0 . runState' "" $ threadedState' +-- (1,("a",())) +``` + +### Membership + +We can do better still. Our programs are far too concerned with the ordering of +their corresponding signatures. The only thing they should care about is whether +the effect exists at all. We can relax this coupling by introducing a new +recursive typeclass: + +```haskell +class Member sub sup where + inj :: sub a -> sup a + prj :: sup a -> Maybe (sub a) +``` + +Here `sub` is said to be a *subtype* of `sup`. `inj` allows us to promote that +subtype to `sup` and `prj` allows us to dynamically downcast back to `sub`. This +typeclass synergizes especially well with `:+:`. For instance, we expect +`State Int` to be a subtype of `State Int :+: State String`. Importantly, we'd +expect the same for `State String`. Let's consider how instances of `Member` +might look. First is reflexivity: + +```haskell +instance Member sig sig where + inj = id + prj = Just +``` + +This instance should be fairly straightforward. We want to be able to cast a +type to and from itself without issue. Next is left-occurrence: + +```haskell +instance Member sig (sig :+: r) where + inj = L + prj (L f) = Just f + prj _ = Nothing +``` + +This is the pattern we've been working with up until now. Casting upwards is +just a matter of using the `L` data constructor while projecting back down works +so long as we are within the `L` context. Likewise there exists a +right-recursion rule: + +```haskell +instance (Member sig r) => Member sig (l :+: r) where + inj = R . inj + prj (R g) = prj g + prj _ = Nothing +``` + +Lastly, as a convenience, we introduce left-recursion: + +```haskell +instance Member sig (l1 :+: (l2 :+: r)) => + Member sig ((l1 :+: l2) :+: r) where + inj sub = case inj sub of + L l1 -> L (L l1) + R (L l2) -> L (R l2) + R (R r) -> R r + prj sup = case sup of + L (L l1) -> prj (L l1) + L (R l2) -> prj (R (L l2)) + R r -> prj (R (R r)) +``` + +The above allows us to operate on a *tree* of types rather than a list. We can +read this as saying "subtying is not affected by how `:+:` is associated." + +```{=html} +<.warning> +``` +These instances will not compile as is. A mix of `TypeApplications` and +`OVERLAPPING` pragmas must be used. Refer to the +[git repository](https://git.jrpotter.com/blog/effect-systems) for the real +implementation. +```{=html} + +``` + +With the above instances in place, we can now create a more flexible +implementation of `threadedState` above: + +```haskell +data Void k deriving Functor + +run :: forall a. Free Void a -> a +run (Pure a) = a +run _ = error (pack "impossible") + +threadedState'' :: + Functor sig => + Member (State Int) sig => + Member (State String) sig => + Free sig () +threadedState'' = + Free (inj (Get @Int (\s1 -> + Free (inj (Get (\s2 -> + Free (inj (Put (s1 + 1) + (Free (inj (Put (s2 ++ "a") + (Pure ())))))))))))) + +-- >>> run . runState' "" . runState' @Int 0 $ threadedState'' +-- ("a",(1,())) +-- >>> run . runState' @Int 0 . runState' "" $ threadedState'' +-- (1,("a",())) +``` + +A few takeaways: + +1. The program now stays polymorphic in type `sig`, +1. We no longer explicitly mention `L` or `R` data constructors, and +1. We use `run` to peel away the final effect. + +This flexibility grants us the ability to *choose* the order we handle effects +at the call site. By writing a few additional smart constructors, we could have +a nicer program still: + +```haskell +inject :: (Member sub sup) => sub (Free sup a) -> Free sup a +inject = Free . inj + +project :: (Member sub sup) => Free sup a -> Maybe (sub (Free sup a)) +project (Free s) = prj s +project _ = Nothing + +get :: Functor sig => Member (State s) sig => Free sig s +get = inject (Get pure) + +put :: Functor sig => Member (State s) sig => s -> Free sig () +put s = inject (Put s (pure ())) + +threadedStateM'' :: + Functor sig => + Member (State Int) sig => + Member (State String) sig => + Free sig () +threadedStateM'' = do + s1 <- get @Int + s2 <- get @String + put (s1 + 1) + put (s2 ++ "a") + pure () +``` + +## Higher-Order Effects + +This composition provides many benefits, but in certain situations we end up +hitting a wall. To continue forward, we borrow an example from +[Effect Handlers in Scope](/assets/pdf/effect-systems/scope.pdf). In particular, +we discuss exception handling and how we can use a free monad to simulate +throwing and catching exceptions. + +```haskell +newtype Throw e k = Throw e deriving (Functor) + +throw e = inject (Throw e) +``` + +```{=html} +<.info> +``` +To avoid too many distractions, we will sometimes skip writing type signatures. +```{=html} + +``` + +This `Throw` type should feel very intuitive at this point. We take an exception +and "inject" it into our program using the `throw` smart constructor. What's the +`catch`? + +```haskell +catch (Pure a) _ = pure a +catch (Free (L (Throw e))) h = h e +catch (Free (R other)) h = Free (fmap (`catch` h) other) +``` + +In this scenario, `catch` traverses our program, happily passing values through +until it encounters a `Throw`. Our respective "peel" looks like so: + +```haskell +runThrow :: forall e a sig. Free (Throw e :+: sig) a -> Free sig (Either e a) +runThrow (Pure a) = pure (Right a) +runThrow (Free (L (Throw e))) = pure (Left e) +runThrow (Free (R other)) = Free (fmap runThrow other) +``` + +We now have the requisite tools needed to build up and execute a sample program +that composes some `State Int` effect with a `Throw` effect: + +```haskell +countDown :: + forall sig. + Functor sig => + Member (State Int) sig => + Member (Throw ()) sig => + Free sig () +countDown = do + decr {- 1 -} + catch (decr {- 2 -} >> decr {- 3 -}) pure + where + decr = do + x <- get @Int + if x > 0 then put (x - 1) else throw () +``` + +This program calls a potentially dangerous `decr` function three times, with the +last two attempts wrapped around a `catch`. + +### Scoping Problems + +How should the state of `countDown` be interpreted? There exist two reasonable +options: + +1. If state is considered **global**, then successful decrements in catch should + persist. That is, our final state would be the initial value decremented + as many times as `decr` succeeds. +1. If state is considered **local**, we expect `catch` to decrement state twice + but to *rollback* if an error is raised. If an error is caught, our final + state would be the initial value decremented just the once. + +This is what it means for an operation to be **scoped**. In the local case, +within the semantics of exception handling, the nested program within `catch` +should not affect the state of the world outside of it in the case of an +exception. Let's see if we can somehow write and invoke handlers accordingly: + +```haskell +>>> run . runThrow @() . runState' @Int 3 $ countDown +Right (0,()) +``` + +The above snippet demonstrates a result we expect in either interpretation. The +nested `decr >> decr` raises no error. Likewise + +```haskell +>>> run . runThrow @() . runState' @Int 0 $ countDown +Left () +``` + +should also feel correct, regardless of interpretation. `decr {- 1 -}` ends up +returning a `throw ()` which the subsequent `runThrow` handler interprets as +`Left`. What about the following? + +```haskell +>>> run . runThrow @() . runState' @Int 2 $ countDown +Right (0,()) +``` + +This is an example of a global interpretation. Here we throw an error on +`decr {- 3 -}` but `decr {- 2 -}`'s effects persist despite existing within the +`catch`. So can we scope the operation? As it turns out, local semantics are out +of reach. "Flattening" the program hopefully makes the reason clearer: + +```haskell +countDown' = + Free (inj (Get @Int (\x -> + let a = \k -> if x > 0 then Free (inj (Put (x - 1) k)) else throw () + in a (catch (Free (inj (Get @Int (\y -> + let b = \k -> if y > 0 then Free (inj (Put (y - 1) k)) else throw () + in b (Free (inj (Get @Int (\z -> + let c = \k -> if z > 0 then Free (inj (Put (z - 1) k)) else throw () + in c (Pure ()))))))))) pure)))) +``` + +It's noisy, but in the above snippet we see there exists no mechanism that +"saves" the state prior to running the nested program. + +### A Stronger Free + +Somehow we need to ensure a nested (e.g. the program scoped within `catch`) does +not "leak" in any way. To support programs within programs (within programs +within programs...) within the already recursively defined free monad, we look +towards a higher-level abstraction for help. According to Wu, Schrijvers, and +Hinze, + +> A more direct solution [to handle some self-contained context] is to model +> scoping constructs with higher-order syntax, where the syntax carries those +> syntax blocks directly. + +What would such a change look like? To answer that, it proves illustrative +understanding why our current definition of `Free` is insufficient. Consider +what it means to "run" our program. We have a handler that traverses the +program, operates on effects it knows how to operate on, and then returns a +slightly less abstract program for the next handler to process. To save state, +we somehow need each handler to refer to a **context** containing state as +defined by the handler prior. + +As a starting point, review our current definition of `Free`: + +```haskell +data Free f a = Pure a | Free (f (Free f a)) +``` + +We see `a` is not something we, the effects library author, are in a position to +manipulate. To actually extract a value to be saved and threaded in a context +though, we at the very least need this ability. So can we introduce some change +that give us this freedom? One idea is: + +```haskell +data Free f a = Pure a | Free (f (Free f) a) +``` + +The change is subtle but has potential provided we can get all the derived type +machinery working on this type instead. Take note! Previously the kind of `f` +was `Type -> Type`. In this new definition, we see it is now +`(Type -> Type) -> (Type -> Type)`. That is, `f` is now a function that maps +one type function to another. We have entered the world of higher-order kinds. + +```{=html} +<.info> +``` +`f` is usually a natural transformation, mapping one functor to another. The +specifics regarding natural transformations aren't too important here. Just note +when we use the term going forward, we mean a functor to functor mapping. +```{=html} + +``` + +Ideally we can extrapolate our learnings so far to this higher-order world. Of +most importance is our `Functor`-constrained type variable `f`. Let's dive a bit +deeper into what it currently buys us. First, take another look at how `fmap` is +used within `Free`'s `Monad` instance: + +```haskell +instance (Functor f) => Monad (Free f) where + Pure a >>= g = g a + Free f >>= g = Free (fmap (>>= g) f) +``` + +Its purpose is to allow *extending* our syntax, chaining different DSL terms +together into a bigger program. When we write e.g. + +```haskell +readThenWrite = do + input <- read + write input +``` + +it is `fmap` that is responsible for piecing the `read` and `write` together. +Second, re-examine a sample handler, e.g. + +```haskell +runState' s (Pure a) = pure (s, a) +runState' s (Free (L (Get f))) = runState' s (f s) +runState' _ (Free (L (Put s f))) = runState' s f +runState' s (Free (R other)) = Free (fmap (runState' s) other) +``` + +In this case, `fmap` is responsible for *weaving* the state semantics throughout +the syntax. This is what allows us to interpret programs comprised of multiple +different syntaxes. Whatever we end up building at the higher level needs to +keep both these aspects in mind. + +### Higher-Order Syntax + +Syntax is the easier of the two to resolve so that's where we'll first avert out +attention. Extension requires two things: + +1. A higher-level concept of a functor to constrain our new `f`, and +1. An `fmap`-like function capable of performing the extension. + +Building out (1) is fairly straightforward. Since `f` corresponds to a natural +transformation, we create a mapping between functors like so: + +```haskell +class HFunctor f where + hmap :: + (Functor m, Functor n) => + (forall x. m x -> n x) -> + (forall x. f m x -> f n x) +``` + +This allows us to lift transformations of e.g. `Identity -> Maybe` into +`f Identity -> f Maybe`. Take a moment to notice the parallels between `fmap` +and `hmap`. Building (2) is equally simple: + +```haskell +class HFunctor f => Syntax f where + emap :: (m a -> m b) -> (f m a -> f m b) +``` + +We designate `emap` as our `fmap`-extending equivalent. This is made obvious by +seeing how `Free` ends up using `emap`: + +```haskell +instance Syntax f => Monad (Free f) where + Pure a >>= g = g a + Free f >>= g = Free (emap (>>= g) f) +``` + +Once again, note the parallels betwen the `Monad` instances of both `Free`s. + +### Higher-Order Semantics + +The more difficult problem lies on the semantic side of the equation. This part +needs to manage the threading of functions throughout potentially nested +effects. To demonstrate, consider a revision to our `Throw` type that includes a +`Catch` at the syntactic level: + +```haskell +data Error e m a = Throw e + | forall x. Catch (m x) (e -> m x) (x -> m a) +``` + +We can create `Error` instances of our `HFunctor` and `Syntax` classes as +follows: + +```haskell +instance HFunctor (Error e) where + hmap _ (Throw x) = Throw x + hmap t (Catch p h k) = Catch (t p) (t . h) (t . k) + +instance Syntax (Error e) where + emap _ (Throw e) = Throw e + emap f (Catch p h k) = Catch p h (f . k) +``` + +This is all well and good, but now suppose we want to write a handler in the +same way we wrote `runThrow` earlier: + +```haskell +runError :: + forall e a sig. + Syntax sig => + Free (Error e :+: sig) a -> + Free sig (Either e a) +runError (Pure a) = pure (Right a) +runError (Free (L (Throw e))) = pure (Left e) +runError (Free (L (Catch p h k))) = + runError p >>= \case + Left e -> + runError (h e) >>= \case + Left e' -> pure (Left e') + Right a -> runError (k a) + Right a -> runError (k a) +runError (Free (R other)) = Free _ +``` + +Make sure everything leading up to the last pattern makes sense and then ask +yourself how you might fill in the hole (`_`). We only have a few tools +at our disposal, namely `hmap` and `emap`. But, no matter how we choose to +compose them, `hmap` will let us down. In particular, our only means of +"peeling" the signature is `runError` which is incompatible with the natural +transformation `hmap` expects. + +--- + +We need another function specific for this weaving behavior, which we choose to +add to the `Syntax` typeclass: + +```haskell +class HFunctor f => Syntax f where + emap :: (m a -> m b) -> (f m a -> f m b) + + weave :: + (Monad m, Monad n, Functor ctx) => + ctx () -> + Handler ctx m n -> + (f m a -> f n (ctx a)) + +type Handler ctx m n = forall x. ctx (m x) -> n (ctx x) +``` + +Pay special attention to `Handler`. By introducing a functorial context (i.e. +`ctx`), we have defined a function signature that more closely reflects that of +`runError`. This is made clearer by instantiating `ctx` to `Either e`: + +```haskell +type Handler m n = forall x. Either e (m x) -> n (Either e x) + +runError :: Free (Error e :+: sig) a -> Free sig (Either e a) +``` + +Without `ctx`, `weave` would look just like `hmap`, highlighting how it's +particularly well-suited to bypassing the `hmap`'s limitations. With `weave` +also comes expanded `Syntax` instances: + +```haskell +instance (Syntax f, Syntax g) => Syntax (f :+: g) where + weave ctx hdl (L f) = L (weave ctx hdl f) + weave ctx hdl (R g) = R (weave ctx hdl g) + +instance Syntax (Error e) where + weave _ _ (Throw x) = Throw x + weave ctx hdl (Catch p h k) = + -- forall x. Catch (m x) (e -> m x) (x -> m a) + Catch + (hdl (fmap (const p) ctx)) + (\e -> hdl (fmap (const (h e)) ctx)) + (hdl . fmap k) +``` + +`const` is used solely to wrap our results in a way that `hdl` expects. With +these instances fully defined, we can now finish our `runError` handler: + +```haskell +runError (Free (R other)) = + Free $ weave (Right ()) (either (pure . Left) runError) other +``` + +### Lifting + +The solution we've developed so far wouldn't be especially useful if it was +weaker than the previous. As mentioned before, our new solution splits the +functionality of `fmap` into extension and weaving. But nothing is stopping us +from defining an instance that continues using `fmap` for both. Consider the +following: + +```haskell +newtype Lift sig (m :: Type -> Type) a = Lift (sig (m a)) +``` + +Here `sig` refers to the lower-order data type we want to elevate to our +higher-order `Free`, e.g. `State s`: + +```haskell +type HState s = Lift (State s) + +hIncrement :: Free (Lift (State Int)) () +hIncrement = Free (Lift (Get (\s -> Free (Lift (Put (s + 1) (Pure ())))))) + +type HVoid = Lift Void + +run :: Free HVoid a -> a +run (Pure a) = a +run _ = error (pack "impossible") +``` + +Here `hIncrement` is a lifted version of `increment` defined before. Likewise, +`run` remains nearly identical to its previous definition. Making `Lift` an +instance of `Syntax` is a equally straightforward: + +```haskell + instance Functor sig => HFunctor (Lift sig) where + hmap t (Lift f) = Lift (fmap t f) + + instance Functor sig => Syntax (Lift sig) where + emap t (Lift f) = Lift (fmap t f) + + weave ctx hdl (Lift f) = Lift (fmap (\p -> hdl (fmap (const p) ctx)) f) +``` + +The corresponding smart constructors and state handler should look like before, +but with our `ctx` now carrying the state around: + +```haskell +get :: forall s sig. HFunctor sig => Member (HState s) sig => Free sig s +get = inject (Lift (Get Pure)) + +put :: forall s sig. HFunctor sig => Member (HState s) sig => s -> Free sig () +put s = inject (Lift (Put s (pure ()))) + +runState :: + forall s a sig. + Syntax sig => + s -> + Free (HState s :+: sig) a -> + Free sig (s, a) +runState s (Pure a) = pure (s, a) +runState s (Free (L (Lift (Get f)))) = runState s (f s) +runState _ (Free (L (Lift (Put s f)))) = runState s f +runState s (Free (R other)) = Free (weave (s, ()) hdl other) + where + hdl :: forall x. (s, Free (HState s :+: sig) x) -> Free sig (s, x) + hdl = uncurry runState +``` + +With all this in place, we can finally construct our `countDown` example again: + +```haskell +countDown :: + forall sig. + Syntax sig => + Member (HState Int) sig => + Member (Error ()) sig => + Free sig () +countDown = do + decr {- 1 -} + catch (decr {- 2 -} >> decr {- 3 -}) pure + where + decr = do + x <- get @Int + if x > 0 then put (x - 1) else throw () +``` + +Now if we encounter an error within our `catch` statement, the local state +semantics are respected: + +```haskell +>>> run . runError @() . runState @Int 2 $ countDown +Right (1,()) +``` + +Pay attention to *why* this works - we first use our `runState` handler and +eventually encounter `decr {- 3 -}` which returns `throw ()` instead of +`put (x - 1)`. During this process, weave was invoked on a `Catch` with context +`(s, )` used to maintain the state at the time. Next `runError` is invoked which +sees the `Catch`, encounters the returned `Throw` after running the scoped +program, and invokes the error handler which has our saved state. + +## Limitations + +Though the higher-order free implementation is largely a useful tool for +managing effects, it is not perfect. I've had an especially hard time getting +resource-oriented effects working, e.g. with custom effects like so: + +```haskell +data Server hdl conn (m :: * -> *) k where + Start :: SpawnOptions -> Server hdl conn m hdl + Stop :: hdl -> Server hdl conn m ExitCode + GetPort :: hdl -> Server hdl conn m PortNumber + Open :: Text -> PortNumber -> Server hdl conn m conn + Close :: conn -> Server hdl conn m () +``` + +The issue here being running the custom `Server` handler invokes `start` *and* +`stop` when wrapped in some [bracket](https://git.jrpotter.com/r/fused-effects-exceptions)-like +interface, even if the bracketed code has not yet finished. I have settled on +workarounds, but these workarounds consist of just structuring these kind of +effects differently. + +In general, modeling asynchronous or `IO`-oriented operations feel "unsolved" +with solutions resorting to some [forklift](https://apfelmus.nfshost.com/blog/2012/06/07-forklift.html) +strategy or other ad-hoc solutions that don't feel as cemented in literature. I +don't necessarily think these are the *wrong* approach (I frankly don't know +enough to have a real opinion here), but it'd be nice to feel there was some +consensus as to what a non-hacky solution theoretically looks like. + +Additionally, it is cumbersome remembering the order handlers should be applied +to achieve the desired global vs. local state semantics. This is not exclusively +a problem of free effect systems (e.g. MTL also suffers from this), but the +issue feels more prominent here. + +## Conclusion + +I will continue exploring effect systems à la free, but I am admittedly not +yet convinced they are the right way forward. Unfortunately, they can be hard to +reason about with unexpected interactions between effects if not careful. I am +sure a large contributing factor to this conclusion is the lack of +beginner-oriented documentation regarding proper use and edge cases. Just to +build up this post required reading source code of multiple effects libraries +and scattered blog posts, watching various YouTube videos, etc. And, despite all +that, I am still not confident I understand the implementation details behind +certain key abstractions. Hopefully this entry threads the needle between +exposition and overt jargon to get us a little closer though. + +[^1]: Though there exists a categorical definition of what makes something **free**, in this case it suffices to substitute "free" with "systematic". diff --git a/lib/portfolio_web/controllers/blog_html/tagless_final_parsing.html.heex b/lib/portfolio_web/controllers/blog_html/tagless_final_parsing.html.heex new file mode 100644 index 0000000..b0a2616 --- /dev/null +++ b/lib/portfolio_web/controllers/blog_html/tagless_final_parsing.html.heex @@ -0,0 +1,859 @@ +
+

Tagless Final Parsing

+

25 Dec 2021

+
+

In his introductory + text, Oleg Kiselyov discusses the tagless final + strategy for implementing DSLs. The approach permits leveraging the + strong typing provided by some host language in a performant way. This + post combines key thoughts from a selection of papers and code written + on the topic. We conclude with an implementation of an interpreter for + a toy language that runs quickly, remains strongly-typed, and can be + extended without modification.

+
+ +
♤♠♤♠♤
+

Introduction

+

To get started, let’s write what our toy language will look + like.

+
digit = ? any number between 0-9 ? ;
+
+(* We ignore any leading 0s *)
+integer = digit, { digit } ;
+e_integer = [ e_integer, ("+" | "-") ]
+          , ( "(", e_integer, ")" | integer )
+          ;
+
+boolean = "true" | "false" ;
+e_boolean = [ e_boolean, ("&&" | "||") ]
+          , ( "(", e_boolean, ")" | boolean )
+          ;
+
+expr = e_integer | e_boolean ;
+

The above expresses addition, subtraction, conjunction, and + disjunction, to be interpreted in the standard way. All operations are + left-associative, with default precedence disrupted via parenthesis + (()). Our goal is to use megaparsec + to interpret programs in this language, raising errors on malformed or + invalidly-typed inputs. We will evaluate interpreter performance on + inputs such as those generated by

+
echo {1..10000000} | sed 's/ / + /g' > input.txt
+ <.info> +

To support inputs like above, ideally we mix lazy and strict + functionality. For example, we should lazily load in a file but + strictly evaluate its contents. Certain languages struggle without + this more nuanced evaluation strategy, e.g.

+
$ (echo -n 'print(' && echo -n {1..10000000} && echo ')') |
+  sed 's/ / + /g' > input.py
+$ python3 input.py
+Segmentation fault: 11
+

Note it’d actually be more efficient on our end to not use + megaparsec at all! The library loads in the entirety of to-be-parsed + text into memory, but using it will hopefully be more representative + of projects out in the wild.

+ +

Initial Encoding

+

How might we instinctively choose to tackle an interpreter of our + language? As one might expect, megaparsec already has all the tooling + needed to parse expressions. We can represent our expressions and + results straightforwardly like so:

+
data Expr
+  = EInt  Integer
+  | EBool Bool
+  | EAdd  Expr Expr
+  | ESub  Expr Expr
+  | EAnd  Expr Expr
+  | EOr   Expr Expr
+  deriving (Show)
+
+data Result = RInt Integer | RBool Bool deriving (Eq)
+

We use a standard ADT to describe the structure of our program, + nesting the same data type recursively to represent precedence. For + instance, we would expect a (so-far fictional) function + parse to give us

+
>>> parse "1 + 2 + 3"
+EAdd (EAdd (EInt 1) (EInt 2)) (EInt 3)
+

We can then evaluate expressions within our Expr type. + Notice we must wrap our result within some Error-like + monad considering the ADT does not necessarily correspond to a + well-typed expression in our language.

+
asInt :: Result -> Either Text Integer
+asInt (RInt e) = pure e
+asInt _ = Left "Could not cast integer."
+
+asBool :: Result -> Either Text Bool
+asBool (RBool e) = pure e
+asBool _ = Left "Could not cast boolean."
+
+toResult :: Expr -> Either Text Result
+toResult (EInt e)  = pure $ RInt e
+toResult (EBool e) = pure $ RBool e
+toResult (EAdd lhs rhs) = do
+  lhs' <- toResult lhs >>= asInt
+  rhs' <- toResult rhs >>= asInt
+  pure $ RInt (lhs' + rhs')
+toResult (ESub lhs rhs) = do
+  lhs' <- toResult lhs >>= asInt
+  rhs' <- toResult rhs >>= asInt
+  pure $ RInt (lhs' - rhs')
+toResult (EAnd lhs rhs) = do
+  lhs' <- toResult lhs >>= asBool
+  rhs' <- toResult rhs >>= asBool
+  pure $ RBool (lhs' && rhs')
+toResult (EOr lhs rhs) = do
+  lhs' <- toResult lhs >>= asBool
+  rhs' <- toResult rhs >>= asBool
+  pure $ RBool (lhs' || rhs')
+

With the above in place, we can begin fleshing out our first + parsing attempt. A naive solution may look as follows:

+
parseNaive :: Parser Result
+parseNaive = expr >>= either (fail . unpack) pure . toResult
+ where
+  expr = E.makeExprParser term
+    [ [binary "+" EAdd, binary "-" ESub]
+    , [binary "&&" EAnd, binary "||" EOr]
+    ]
+
+  binary name f = E.InfixL (f <$ symbol name)
+
+  term = parens expr <|> EInt <$> integer <|> EBool <$> boolean
+

There are certainly components of the above implementation that + raises eyebrows (at least in the context of large inputs), but that + could potentially be overlooked depending on how well it runs. Running + parseNaive1 against input.txt + shows little promise though:

+
$ cabal run --enable-profiling exe:initial-encoding -- input.txt +RTS -s
+50000005000000
+...
+            4126 MiB total memory in use (0 MB lost due to fragmentation)
+...
+  Total   time   25.541s  ( 27.121s elapsed)
+...
+  Productivity  82.3% of total user, 78.9% of total elapsed
+

A few immediate takeaways from this exercise:

+

This solution

+
    +
  • has to run through the generated AST twice. First to build the AST + and second to type-check and evaluate the code + (i.e. toResult).
  • +
  • is resource intensive. It consumes a large amount of memory + (approximately 4 GiB in the above run) and is far too + slow. A stream of 10,000,000 integers should be quick and cheap to sum + together.
  • +
  • is unsafe. Packaged as a library, there are no typing guarantees + we leverage from our host language. For example, expression + EAnd (EInt 1) (EInt 2) is valid.
  • +
  • suffers from the expression + problem. Developers cannot extend our language with new operations + without having to modify the source.
  • +
+

Let’s tackle each of these problems in turn.

+

Single Pass

+

Our naive attempt separated parsing from evaluation because they + fail in different ways. The former raises a + ParseErrorBundle on invalid input while the latter raises + a Text on mismatched types. Ideally we would have a + single error type shared by both of these failure modes. + Unfortunately, this is not so simple - the Operator types + found within makeExprParser have incompatible function + signatures. In particular, InfixL is defined as

+
InfixL :: m (a -> a -> a) -> Operator m a
+

instead of

+
InfixL :: (a -> a -> m a) -> Operator m a
+

To work around these limitations, we define our binary functions to + operate and return on values of type Either Text Expr. We + then raise a Left on type mismatch, deferring error + reporting at the Parser level until the current + expression is fully parsed:

+
parseSingle :: Parser Result
+parseSingle = expr >>= either (fail . unpack) pure
+ where
+  expr = E.makeExprParser term
+    [ [binary "+"  asInt  EInt  EAdd, binary "-"  asInt  EInt  ESub]
+    , [binary "&&" asBool EBool EAnd, binary "||" asBool EBool EOr ]
+    ]
+
+  binary name cast f bin = E.InfixL do
+    void $ symbol name
+    pure $ \lhs rhs -> do
+      lhs' <- lhs >>= cast
+      rhs' <- rhs >>= cast
+      toResult $ bin (f lhs') (f rhs')
+
+  term = parens expr <|>
+         Right . RInt <$> integer <|>
+         Right . RBool <$> boolean
+

Resource Usage

+

Though our above strategy avoids two explicit passes + through the AST, laziness sets it up so that we build this intertwined + callstack without actually evaluating anything until we interpret the + returned Result. The generated runtime statistics are + actually worse as a result:

+
cabal run --enable-profiling exe:initial-encoding -- \
+  input.txt -m single +RTS -s
+

We’ll use a heap profile to make the implementation’s shortcomings + more obvious:

+
cabal run --enable-profiling exe:initial-encoding -- \
+  input.txt -m single +RTS -hr
+ <.tip> +

To iterate faster, reduce the number of integers in + input.txt. The resulting heap profile should remain + proportional to the original. Just make sure the program runs for long + enough to actually perform meaningful profiling. This section works on + inputs of 100,000 integers.

+ +

The generated heap profile, broken down by retainer set, looks as + follows:

+
+ parser-initial-100k-heap-hr-single + +
+

Our top-level expression parser continues growing in size until the + program nears completion, presumably when the expression is finally + evaluated. To further pinpoint the leaky methods, we re-run the heap + generation by cost-centre stack to get:

+
+ parser-initial-100k-heap-hc-single + +
+

Even our straightforward lexing functions are holding on to memory. + The laziness and left associative nature of our grammar hints that we + may be dealing with a thunk stack reminiscient of foldl. This + implies we could fix the issue by evaluating our data strictly with + e.g. deepseq. The + problem is unfortunately deeper than this though. Consider the + definition of InfixL’s + parser:

+
pInfixL :: MonadPlus m => m (a -> a -> a) -> m a -> a -> m a
+pInfixL op p x = do
+  f <- op
+  y <- p
+  let r = f x y
+  pInfixL op p r <|> return r
+

Because this implementation uses the backtracking alternative + operator (<|>), we must also hold onto each + <|> return r in memory “just in case” we need to + use it. Since we are working with a left-factored + grammar, we can drop the alternatives and apply strictness with a + custom expression parser:

+
parseStrict :: Parser Result
+parseStrict = term >>= expr
+ where
+  expr t = do
+    op <- M.option Nothing $ Just <$> ops
+    case op of
+      Just OpAdd -> nest t asInt  EInt  EAdd
+      Just OpSub -> nest t asInt  EInt  ESub
+      Just OpAnd -> nest t asBool EBool EAnd
+      Just OpOr  -> nest t asBool EBool EOr
+      _          -> pure t
+
+  nest
+    :: forall a
+     . Result
+    -> (Result -> Either Text a)
+    -> (a -> Expr)
+    -> (Expr -> Expr -> Expr)
+    -> Parser Result
+  nest t cast f bin = do
+    t' <- term
+    a <- either (fail . unpack) pure do
+      lhs <- cast t
+      rhs <- cast t'
+      toResult $ bin (f lhs) (f rhs)
+    a `deepseq` expr a
+
+  term = do
+    p <- M.option Nothing $ Just <$> symbol "("
+    if isJust p then (term >>= expr) <* symbol ")" else
+      RInt <$> integer <|> RBool <$> boolean
+

This implementation runs marginally faster and uses a constant + amount of memory:

+
+ parser-initial-100k-heap-hd-strict + +
+ <.info> +

ARR_WORDS corresponds to the ByteString + constructor and is unavoidable so long as we use megaparsec.

+ +

To get a better sense of where runtime tends to reside, let’s + re-run our newly strict implementation on our 10,000,000 file again + alongside a time profile:

+
echo {1..10000000} | sed 's/ / + /g' > input.txt
+cabal run --enable-profiling exe:initial-encoding -- \
+  input.txt -m strict +RTS -p
+
space          Text.Megaparsec.Lexer      Text/Megaparsec/Lexer.hs:(68,1)-(71,44)                 54.9   57.3
+decimal        Text.Megaparsec.Char.Lexer Text/Megaparsec/Char/Lexer.hs:363:1-32                  24.7   21.4
+lexeme         Text.Megaparsec.Lexer      Text/Megaparsec/Lexer.hs:87:1-23                        12.6   15.7
+ops            Parser.Utils               src/Parser/Utils.hs:(69,1)-(74,3)                        2.6    2.7
+toResult       Parser.Initial             src/Parser/Initial.hs:(62,1)-(79,29)                     2.3    2.2
+run            Main                       app/Main.hs:(42,1)-(54,58)                               1.6    0.6
+readTextDevice Data.Text.Internal.IO      libraries/text/src/Data/Text/Internal/IO.hs:133:39-64    1.3    0.0
+

Surprisingly, the vast majority of time (roughly 95%) is spent + parsing. As such, we won’t worry ourselves about runtime any + further.

+

Type Safety

+

Let’s next explore how we can empower library users with a stricter + version of the Expr monotype. In particular, we want to + prohibit construction of invalid expressions. A common strategy is to + promote our ADT to a GADT:

+
data GExpr a where
+  GInt  :: Integer -> GExpr Integer
+  GBool :: Bool    -> GExpr Bool
+  GAdd  :: GExpr Integer -> GExpr Integer -> GExpr Integer
+  GSub  :: GExpr Integer -> GExpr Integer -> GExpr Integer
+  GAnd  :: GExpr Bool    -> GExpr Bool    -> GExpr Bool
+  GOr   :: GExpr Bool    -> GExpr Bool    -> GExpr Bool
+

By virtue of working with arbitrary, potentially mis-typed input, + we must eventually perform some form of type-checking on our + input. To get the GADT representation working requires additional + machinery like existential datatypes and Rank2Types. The + end user of our library reaps the benefits though, acquiring a + strongly-typed representation of our AST:

+
data Wrapper = forall a. Show a => Wrapper (GExpr a)
+
+parseGadt :: Parser Wrapper
+parseGadt = term >>= expr
+ where
+  ...
+  nest
+    :: forall b
+     . Show b
+    => Wrapper
+    -> (forall a. GExpr a -> Either Text (GExpr b))
+    -> (b -> GExpr b)
+    -> (GExpr b -> GExpr b -> GExpr b)
+    -> Parser Wrapper
+  nest (Wrapper t) cast f bin = do
+    Wrapper t' <- term
+    case (cast t, cast t') of
+      (Right lhs, Right rhs) -> do
+        let z = eval $ bin lhs rhs
+        z `seq` expr (Wrapper $ f z)
+      (Left e, _) -> fail $ unpack e
+      (_, Left e) -> fail $ unpack e
+

We hide the full details here (refer to the linked Github + repository for the full implementation), but note the changes are + minimal outside of the signatures/data types required to make our + existentially quantified type work. Users of the parser can now unwrap + the Wrapper type and resume like normal.

+ <.warning> +

This is an arguable "improvement" considering convenience + takes a dramatic hit. It is awkward working with the + Wrapper type.

+ +

Expression + Problem

+

Lastly comes the expression problem, and one that is fundamentally + unsolvable given our current implementation. By nature of (G)ADTs, all + data-types are closed. It is not extensible since that would + break any static type guarantees around e.g. pattern matching. To fix + this (and other problems still present in our implementation), we + contrast our initial encoding to that of tagless final.

+

Tagless Final

+

Let’s re-think our GADT example above and refactor it into a + typeclass:

+
class Symantics repr where
+  eInt  :: Integer -> repr Integer
+  eBool :: Bool    -> repr Bool
+  eAdd  :: repr Integer -> repr Integer -> repr Integer
+  eSub  :: repr Integer -> repr Integer -> repr Integer
+  eAnd  :: repr Bool    -> repr Bool    -> repr Bool
+  eOr   :: repr Bool    -> repr Bool    -> repr Bool
+

This should look familiar. Instances of GExpr have + been substituted by a parameter repr of kind + * -> *. Kiselyov describes typeclasses used this way + as a means of defining a class of interpreters. An example + interpreter could look like evaluation from before:

+
newtype Eval a = Eval {runEval :: a}
+
+instance Symantics Eval where
+  eInt  = Eval
+  eBool = Eval
+  eAdd (Eval lhs) (Eval rhs) = Eval (lhs + rhs)
+  eSub (Eval lhs) (Eval rhs) = Eval (lhs - rhs)
+  eAnd (Eval lhs) (Eval rhs) = Eval (lhs && rhs)
+  eOr  (Eval lhs) (Eval rhs) = Eval (lhs || rhs)
+

To belabor the similarities between the two a bit further, compare + the following two examples side-by-side:

+
let expr = foldl' eAdd (eInt 0) $ take count (eInt <$> [1..])
+let expr = foldl' EAdd (EInt 0) $ take count (EInt <$> [1..])
+

Outside of some capitalization, the encodings are exactly the same. + Considering these similarities, it’s clear type safety is not a + concern like it was with Expr. This new paradigm also + allows us to write both an implementation that parallels the memory + usage of above as well as a proper + solution to the expression problem. To follow along though first + requires a quick detour into leibniz equalities.

+

Leibniz + Equality

+

Leibniz equality states that two objects are equivalent provided + they can be substituted in all contexts without issue. Consider the + definition provided by the eq package:

+
newtype a := b = Refl {subst :: forall c. c a -> c b}
+

Here we are saying two types a and b + actually refer to the same type if it turns out that + simultaneously

+
Maybe a ~ Maybe b
+Either String a ~ Either String b
+Identity a ~ Identity b
+...
+

and so on, where ~ refers to equality + constraints. Let’s clarify further with an example. Suppose we had + types A and B and wanted to ensure they were + actually the same type. Then there must exist a function + subst with signature c A -> c B for + all c. What might that function look like? It turns + out there is only one acceptable choice: id.

+

This might not seem particularly valuable at first glance, but what + it does permit is a means of proving equality at the + type-level and in a way Haskell’s type system respects. For instance, + the following is valid:

+
>>> import Data.Eq.Type ((:=)(..))
+>>> :set -XTypeOperators
+
+-- Type-level equality is reflexive. I can prove it since
+-- `id` is a suitable candidate for `subst`.
+>>> refl = Refl id
+>>> :t refl
+refl :: b := b
+
+-- Haskell can verify our types truly are the same.
+>>> a = refl :: (Integer := Integer)
+>>> a = refl :: (Integer := Bool)
+
+<interactive>:7:5: error:
+Couldn't match typeInteger’ with ‘Bool
+

We can also prove less obvious things at the type-level and take + advantage of this later on. As an exercise, consider how you might + express the following:

+
functionEquality
+  :: a1 := a2
+  -> b1 := b2
+  -> (a1 -> b1) := (a2 -> b2)
+

That is, if a1 and a2 are the same types, + and b1 and b2 are the same types, then + functions of type a1 -> b1 can equivalently be + expressed as functions from a2 -> b2.

+ <.accordion header="Answer"> +

I suggest reading from the bottom up to better understand why this + works.

+
import Data.Eq.Type ((:=)(..), refl)
+
+newtype F1 t b a = F1 {runF1 :: t := (a -> b)}
+newtype F2 t a b = F2 {runF2 :: t := (a -> b)}
+
+functionEquality
+  :: forall a1 a2 b1 b2
+   . a1 := a2
+  -> b1 := b2
+  -> (a1 -> b1) := (a2 -> b2)
+functionEquality
+  (Refl s1)  -- s1 :: forall c. c a1 -> c a2
+  (Refl s2)  -- s2 :: forall c. c b1 -> c b2
+  = runF2    -- (a1 -> b1) := (a2 -> b2)
+  . s2       -- F2 (a1 -> b1) a2 b2
+  . F2       -- F2 (a1 -> b1) a2 b1
+  . runF1    -- (a1 -> b1) := (a2 -> b1)
+  . s1       -- F1 (a1 -> b1) b1 a2
+  . F1       -- F1 (a1 -> b1) b1 a1
+  $ refl     -- (a1 -> b1) := (a1 -> b1)
+ +

Dynamics

+

Within our GADTs example, we introduced data type + Wrapper to allow us to pass around GADTs of internally + different type (e.g. GExpr Integer vs. + GExpr Bool) within the same context. We can do something + similar via Dynamics in the tagless final world. Though + we could use the already available dynamics + library, we’ll build our own for exploration’s sake.

+

First, let’s create a representation of the types in our + grammar:

+
class Typeable repr where
+  pInt  :: repr Integer
+  pBool :: repr Bool
+
+newtype TQ t = TQ {runTQ :: forall repr. Typeable repr => repr t}
+
+instance Typeable TQ where
+  pInt  = TQ pInt
+  pBool = TQ pBool
+

TQ takes advantage of polymorphic constructors to + allow us to wrap any compatible Typeable member function + and “reinterpret” it as something else. For example, we can create new + Typeable instances like so:

+
newtype AsInt a = AsInt (Maybe (a := Integer))
+
+instance Typeable AsInt where
+  pInt  = AsInt (Just refl)
+  pBool = AsInt Nothing
+
+newtype AsBool a = AsBool (Maybe (a := Bool))
+
+instance Typeable AsBool where
+  pInt  = AsBool Nothing
+  pBool = AsBool (Just refl)
+

We can then use TQ to check if something is of the + appropriate type:

+
>>> import Data.Maybe (isJust)
+>>> tq = pInt :: TQ Integer
+>>> case runTQ tq of AsInt a -> isJust a
+True
+>>> case runTQ tq of AsBool a -> isJust a
+False
+

Even more interestingly, we can bundle this type representation + alongside a value of the corresponding type, yielding our desired + Dynamic:

+
import qualified Data.Eq.Type as EQ
+
+data Dynamic repr = forall t. Dynamic (TQ t) (repr t)
+
+class IsDynamic a where
+  type' :: forall repr. Typeable repr => repr a
+  lift' :: forall repr. Symantics repr => a -> repr a
+  cast' :: forall repr t. TQ t -> Maybe (t := a)
+
+instance IsDynamic Integer where
+  type' = pInt
+  lift' = eInt
+  cast' (TQ t) = case t of AsInt a -> a
+
+instance IsDynamic Bool where
+  type' = pBool
+  lift' = eBool
+  cast' (TQ t) = case t of AsBool a -> a
+
+toDyn :: forall repr a. IsDynamic a => Symantics repr => a -> Dynamic repr
+toDyn = Dynamic type' . lift'
+
+fromDyn :: forall repr a. IsDynamic a => Dynamic repr -> Maybe (repr a)
+fromDyn (Dynamic t e) = case t of
+  (cast' -> r) -> do
+    r' <- r
+    pure $ EQ.coerce (EQ.lift r') e
+
+
>>> a = toDyn 5           :: Dynamic Expr
+>>> runExpr <$> fromDyn a :: Maybe Integer
+Just 5
+

By maintaining a leibniz equality within our Dynamic + instances (i.e. AsInt), we can internally coerce the + wrapped value into the actual type we care about. With this background + information in place, we can finally devise an expression parser + similar to the parser we’ve written using initial encoding:

+
parseStrict
+  :: forall repr
+   . NFData (Dynamic repr)
+  => Symantics repr
+  => Parser (Dynamic repr)
+parseStrict = term >>= expr
+ where
+  expr :: Dynamic repr -> Parser (Dynamic repr)
+  expr t = do
+    op <- M.option Nothing $ Just <$> ops
+    case op of
+      Just OpAdd -> nest t eAdd OpAdd
+      Just OpSub -> nest t eSub OpSub
+      Just OpAnd -> nest t eAnd OpAnd
+      Just OpOr  -> nest t eOr  OpOr
+      _          -> pure t
+
+  nest
+    :: forall a
+     . IsDynamic a
+    => Dynamic repr
+    -> (repr a -> repr a -> repr a)
+    -> Op
+    -> Parser (Dynamic repr)
+  nest t bin op = do
+    t' <- term
+    case binDyn bin t t' of
+      Nothing -> fail $ "Invalid operands for `" <> show op <> "`"
+      Just a -> a `deepseq` expr a
+
+  term :: Parser (Dynamic repr)
+  term = do
+    p <- M.option Nothing $ Just <$> symbol "("
+    if isJust p then (term >>= expr) <* symbol ")" else
+      toDyn <$> integer <|> toDyn <$> boolean
+

Interpretations

+

So far we’ve seen very little benefit switching to this strategy + despite the level of complexity this change introduces. Here we’ll + pose a question that hopefully makes at least one benefit more + obvious. Suppose we wanted to interpret the parsed expression in two + different ways. First, we want a basic evaluator, and second we want a + pretty-printer. In our initial encoding strategy, the evaluator has + already been defined:

+
eval :: GExpr a -> a
+

We say eval is one possible interpreter over + GExpr. It takes in GExprs and reduces them + into literal values. How would a pretty printer work? One candidate + interpreter could look as follows:

+
pPrint :: GExpr a -> Text
+pPrint (GInt e)  = pack $ show e
+pPrint (GBool e) = pack $ show e
+pPrint (GAdd lhs rhs) = "(" <> pPrint lhs <> " + " <> pPrint rhs <> ")"
+...
+

Unfortunately using this definition requires fundamentally changing + how our GADT parser works. parseGadt currently makes + certain optimizations based on the fact only eval has + been needed so far, reducing the expression as we traverse the input + stream. Generalizing the parser to take in any function of signature + forall b. (forall a. GExpr a) -> b (i.e. the signature + of some generic interpreter) would force us to retain memory or accept + additional arguments to make our function especially generic to + accommodate.2

+

On the other hand, our tagless final approach expects multiple + interpretations from the outset. We can define a newtype + like

+
newtype PPrint a = PPrint {runPPrint :: a}
+
+instance Symantics PPrint where
+  eInt  = PPrint . pack . show
+  eBool = PPrint . pack . show
+  eAdd (PPrint lhs) (PPrint rhs) = PPrint $ "(" <> lhs <> " + " <> rhs <> ")"
+  ...
+

and interpret our

+
parseStrict :: forall repr. Symantics repr => Parser (Dynamic repr)
+

Parser as a Dynamic PPrint instead of a + Dynamic Eval without losing any previously acquired + gains.

+

Expression + Revisited

+

There does exist a major caveat with our tagless final + interpreters. If owning a single Dynamic instance, how + exactly are we able to interpret this in multiple ways? After all, a + Dynamic cannot be of type Dynamic Eval + and Dynamic PPrint. What we’d like to be able to + do is maintain a generic Dynamic repr and reinterpret it + at will.

+

One solution comes in the form of another newtype + around a polymorphic constructor:

+
newtype SQ a = SQ {runSQ :: forall repr. Symantics repr => repr a}
+
+instance Symantics SQ where
+  eInt  e = SQ (eInt e)
+  eBool e = SQ (eBool e)
+  eAdd (SQ lhs) (SQ rhs) = SQ (eAdd lhs rhs)
+  eSub (SQ lhs) (SQ rhs) = SQ (eSub lhs rhs)
+  eAnd (SQ lhs) (SQ rhs) = SQ (eAnd lhs rhs)
+  eOr  (SQ lhs) (SQ rhs) = SQ (eOr  lhs rhs)
+

We can then run evaluation and pretty-printing on the same + entity:

+
data Result = RInt Integer | RBool Bool
+
+runBoth :: Dynamic SQ -> Maybe (Result, Text)
+runBoth d = case fromDyn d of
+  Just (SQ q) -> pure ( case q of Eval a -> RInt a
+                      , case q of PPrint a -> a
+                      )
+  Nothing -> case fromDyn d of
+    Just (SQ q) -> pure ( case q of Eval a -> RBool a
+                        , case q of PPrint a -> a
+                        )
+    Nothing -> Nothing
+

This has an unintended side effect though. By using + SQ, we effectively close our type universe. Suppose we + now wanted to extend our Symantics type with a new + multiplication operator (*). We could do so by writing + typeclass:

+
class MulSymantics repr where
+  eMul :: repr Integer -> repr Integer -> repr Integer
+
+instance MulSymantics Eval where
+  eMul (Eval lhs) (Eval rhs) = Eval (lhs * rhs)
+
+instance MulSymantics PPrint where
+  eMul (PPrint lhs) (PPrint rhs) = PPrint $ "(" <> lhs <> " * " <> rhs <> ")"
+ <.warning> +

Naturally we also need to extend our parser to be aware of the new + operator as well. To avoid diving yet further into the weeds, we do + not do that here.

+ +

But this typeclass is excluded from our SQ type. We’re + forced to write yet another SQ-like wrapper, e.g.

+
newtype MSQ a = MSQ {runMSQ :: forall repr. MulSymantics repr => repr a}
+

just to keep up. This in turn forces us to redefine all functions + that operated on SQ.

+

Copy Symantics

+

We can reformulate this more openly, abandoning any sort of + Rank2 constructors within our newtypes by + choosing to track multiple representations simultaneously:

+
data SCopy repr1 repr2 a = SCopy (repr1 a) (repr2 a)
+
+instance (Symantics repr1, Symantics repr2)
+  => Symantics (SCopy repr1 repr2) where
+  eInt e  = SCopy (eInt e) (eInt e)
+  eBool e = SCopy (eBool e) (eBool e)
+  eAdd (SCopy a1 a2) (SCopy b1 b2) = SCopy (eAdd a1 b1) (eAdd a2 b2)
+  eSub (SCopy a1 a2) (SCopy b1 b2) = SCopy (eSub a1 b1) (eSub a2 b2)
+  eAnd (SCopy a1 a2) (SCopy b1 b2) = SCopy (eAnd a1 b1) (eAnd a2 b2)
+  eOr  (SCopy a1 a2) (SCopy b1 b2) = SCopy (eOr  a1 b1) (eOr  a2 b2)
+
+instance (MulSymantics repr1, MulSymantics repr2)
+  => MulSymantics (SCopy repr1 repr2) where
+  eMul (SCopy a1 a2) (SCopy b1 b2) = SCopy (eMul a1 b1) (eMul a2 b2)
+

As we define new classes of operators on our Integer + and Bool types, we make SCopy an instance of + them. We can then “thread” the second representation throughout our + function calls like so:

+
runEval'
+  :: forall repr
+   . Dynamic (SCopy Eval repr)
+  -> Maybe (Result, Dynamic repr)
+runEval' d = case fromDyn d :: Maybe (SCopy Eval repr Integer) of
+  Just (SCopy (Eval a) r) -> pure (RInt a, Dynamic pInt r)
+  Nothing -> case fromDyn d :: Maybe (SCopy Eval repr Bool) of
+    Just (SCopy (Eval a) r) -> pure (RBool a, Dynamic pBool r)
+    Nothing -> Nothing
+
+runPPrint'
+  :: forall repr
+   . Dynamic (SCopy PPrint repr)
+  -> Maybe (Text, Dynamic repr)
+runPPrint' d = case fromDyn d :: Maybe (SCopy PPrint repr Text) of
+  Just (SCopy (PPrint a) r) -> pure (a, Dynamic pText r)
+  Nothing -> Nothing
+
+runBoth'
+  :: forall repr
+   . Dynamic (SCopy Eval (SCopy PPrint repr))
+  -> Maybe (Result, Text, Dynamic repr)
+runBoth' d = do
+  (r, d') <- runEval' d
+  (p, d'') <- runPPrint' d'
+  pure (r, p, d'')
+

Notice each function places a Dynamic repr of unknown + representation in the last position of each return tuple. The caller + is then able to interpret this extra repr as they wish, + composing them in arbitrary ways (e.g. runBoth').

+

Limitations

+

The expression problem is only partially solved with our + Dynamic strategy. If for instance we wanted to add a new + literal type, e.g. a String, we would unfortunately need + to append to the Typeable and Dynamic + definitions to support them. The standard dynamics + package only allows monomorphic values so in this sense we are stuck. + If only needing to add additional functionality to the existing set of + types though, we can extend at will.

+

Conclusion

+

I was initially hoping to extend this post further with a + discussion around explicit sharing as noted here, + but this post is already getting too long. I covered only a portion of + the topics Oleg Kiselyov wrote about, but covered at least the + majority of topics I’ve so far been exploring in my own personal + projects. I will note that the tagless final approach, while certainly + useful, also does add a fair level of cognitive overhead in my + experience. Remembering the details around dynamics especially is what + prompted me to write this post to begin with.

+ +
diff --git a/lib/portfolio_web/controllers/blog_html/tagless_final_parsing.md b/lib/portfolio_web/controllers/blog_html/tagless_final_parsing.md new file mode 100644 index 0000000..c9a64da --- /dev/null +++ b/lib/portfolio_web/controllers/blog_html/tagless_final_parsing.md @@ -0,0 +1,915 @@ +--- +title: Tagless Final Parsing +date: 25 Dec 2021 +abstract: | + In his [introductory text](/assets/pdf/tagless-final-parsing/kiselyov-interpreters.pdf), + Oleg Kiselyov discusses the **tagless final** strategy for implementing DSLs. + The approach permits leveraging the strong typing provided by some host + language in a performant way. This post combines key thoughts from a + selection of papers and code written on the topic. We conclude with an + implementation of an interpreter for a toy language that runs quickly, + remains strongly-typed, and can be extended without modification. +--- + +## Introduction + +To get started, let's write what our toy language will look like. + +```ebnf +digit = ? any number between 0-9 ? ; + +(* We ignore any leading 0s *) +integer = digit, { digit } ; +e_integer = [ e_integer, ("+" | "-") ] + , ( "(", e_integer, ")" | integer ) + ; + +boolean = "true" | "false" ; +e_boolean = [ e_boolean, ("&&" | "||") ] + , ( "(", e_boolean, ")" | boolean ) + ; + +expr = e_integer | e_boolean ; +``` + +The above expresses addition, subtraction, conjunction, and disjunction, to be +interpreted in the standard way. All operations are left-associative, with +default precedence disrupted via parenthesis (`()`). Our goal is to use +[megaparsec](https://hackage.haskell.org/package/megaparsec) to interpret +programs in this language, raising errors on malformed or invalidly-typed +inputs. We will evaluate interpreter performance on inputs such as those +generated by + +```bash +echo {1..10000000} | sed 's/ / + /g' > input.txt +``` + +```{=html} +<.info> +``` +To support inputs like above, ideally we mix lazy and strict functionality. For +example, we should lazily load in a file but strictly evaluate its contents. +Certain languages struggle without this more nuanced evaluation strategy, e.g. +```bash +$ (echo -n 'print(' && echo -n {1..10000000} && echo ')') | + sed 's/ / + /g' > input.py +$ python3 input.py +Segmentation fault: 11 +``` +Note it'd actually be more efficient on our end to not use megaparsec at all! +The library loads in the entirety of to-be-parsed text into memory, but using +it will hopefully be more representative of projects out in the wild. +```{=html} + +``` + +## Initial Encoding + +How might we instinctively choose to tackle an interpreter of our language? As +one might expect, megaparsec already has all the tooling needed to parse +expressions. We can represent our expressions and results straightforwardly like +so: + +```haskell +data Expr + = EInt Integer + | EBool Bool + | EAdd Expr Expr + | ESub Expr Expr + | EAnd Expr Expr + | EOr Expr Expr + deriving (Show) + +data Result = RInt Integer | RBool Bool deriving (Eq) +``` + +We use a standard ADT to describe the structure of our program, nesting the +same data type recursively to represent precedence. For instance, we would +expect a (so-far fictional) function `parse` to give us + +```haskell +>>> parse "1 + 2 + 3" +EAdd (EAdd (EInt 1) (EInt 2)) (EInt 3) +``` + +We can then evaluate expressions within our `Expr` type. Notice we must wrap our +result within some `Error`-like monad considering the ADT does not necessarily +correspond to a well-typed expression in our language. + +```haskell +asInt :: Result -> Either Text Integer +asInt (RInt e) = pure e +asInt _ = Left "Could not cast integer." + +asBool :: Result -> Either Text Bool +asBool (RBool e) = pure e +asBool _ = Left "Could not cast boolean." + +toResult :: Expr -> Either Text Result +toResult (EInt e) = pure $ RInt e +toResult (EBool e) = pure $ RBool e +toResult (EAdd lhs rhs) = do + lhs' <- toResult lhs >>= asInt + rhs' <- toResult rhs >>= asInt + pure $ RInt (lhs' + rhs') +toResult (ESub lhs rhs) = do + lhs' <- toResult lhs >>= asInt + rhs' <- toResult rhs >>= asInt + pure $ RInt (lhs' - rhs') +toResult (EAnd lhs rhs) = do + lhs' <- toResult lhs >>= asBool + rhs' <- toResult rhs >>= asBool + pure $ RBool (lhs' && rhs') +toResult (EOr lhs rhs) = do + lhs' <- toResult lhs >>= asBool + rhs' <- toResult rhs >>= asBool + pure $ RBool (lhs' || rhs') +``` + +With the above in place, we can begin fleshing out our first parsing attempt. A +naive solution may look as follows: + +```haskell +parseNaive :: Parser Result +parseNaive = expr >>= either (fail . unpack) pure . toResult + where + expr = E.makeExprParser term + [ [binary "+" EAdd, binary "-" ESub] + , [binary "&&" EAnd, binary "||" EOr] + ] + + binary name f = E.InfixL (f <$ symbol name) + + term = parens expr <|> EInt <$> integer <|> EBool <$> boolean +``` + +There are certainly components of the above implementation that raises eyebrows +(at least in the context of large inputs), but that could potentially be +overlooked depending on how well it runs. Running `parseNaive`[^1] against +`input.txt` shows little promise though: + +```bash +$ cabal run --enable-profiling exe:initial-encoding -- input.txt +RTS -s +50000005000000 +... + 4126 MiB total memory in use (0 MB lost due to fragmentation) +... + Total time 25.541s ( 27.121s elapsed) +... + Productivity 82.3% of total user, 78.9% of total elapsed + +``` + +A few immediate takeaways from this exercise: + +This solution + +- has to run through the generated AST twice. First to build the AST and second + to type-check and evaluate the code (i.e. `toResult`). +- is resource intensive. It consumes a large amount of memory (approximately + `4` GiB in the above run) and is far too slow. A stream of 10,000,000 integers + should be quick and cheap to sum together. +- is unsafe. Packaged as a library, there are no typing guarantees we leverage + from our host language. For example, expression `EAnd (EInt 1) (EInt 2)` is + valid. +- suffers from the [expression problem](https://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt). + Developers cannot extend our language with new operations without having to + modify the source. + +Let's tackle each of these problems in turn. + +### Single Pass + +Our naive attempt separated parsing from evaluation because they fail in +different ways. The former raises a `ParseErrorBundle` on invalid input while +the latter raises a `Text` on mismatched types. Ideally we would have a single +error type shared by both of these failure modes. Unfortunately, this is not so +simple - the `Operator` types found within `makeExprParser` have incompatible +function signatures. In particular, `InfixL` is defined as + +```haskell +InfixL :: m (a -> a -> a) -> Operator m a +``` + +instead of + +```haskell +InfixL :: (a -> a -> m a) -> Operator m a +``` + +To work around these limitations, we define our binary functions to operate and +return on values of type `Either Text Expr`. We then raise a `Left` on type +mismatch, deferring error reporting at the `Parser` level until the current +expression is fully parsed: + +```haskell +parseSingle :: Parser Result +parseSingle = expr >>= either (fail . unpack) pure + where + expr = E.makeExprParser term + [ [binary "+" asInt EInt EAdd, binary "-" asInt EInt ESub] + , [binary "&&" asBool EBool EAnd, binary "||" asBool EBool EOr ] + ] + + binary name cast f bin = E.InfixL do + void $ symbol name + pure $ \lhs rhs -> do + lhs' <- lhs >>= cast + rhs' <- rhs >>= cast + toResult $ bin (f lhs') (f rhs') + + term = parens expr <|> + Right . RInt <$> integer <|> + Right . RBool <$> boolean +``` + +### Resource Usage + +Though our above strategy avoids two *explicit* passes through the AST, laziness +sets it up so that we build this intertwined callstack without actually +evaluating anything until we interpret the returned `Result`. The generated +runtime statistics are actually worse as a result: + +```bash +cabal run --enable-profiling exe:initial-encoding -- \ + input.txt -m single +RTS -s +``` + +We'll use a heap profile to make the implementation's shortcomings more obvious: + +```bash +cabal run --enable-profiling exe:initial-encoding -- \ + input.txt -m single +RTS -hr +``` + +```{=html} +<.tip> +``` +To iterate faster, reduce the number of integers in `input.txt`. The resulting +heap profile should remain proportional to the original. Just make sure the +program runs for long enough to actually perform meaningful profiling. This +section works on inputs of 100,000 integers. +```{=html} + +``` + +The generated heap profile, broken down by retainer set, looks as follows: + +![parser-initial-100k-heap-hr-single](/images/tagless-final-parsing/parser-initial-100k-heap-hr-single.png) + +Our top-level expression parser continues growing in size until the program +nears completion, presumably when the expression is finally evaluated. To +further pinpoint the leaky methods, we re-run the heap generation by cost-centre +stack to get: + +![parser-initial-100k-heap-hc-single](/images/tagless-final-parsing/parser-initial-100k-heap-hc-single.png) + +Even our straightforward lexing functions are holding on to memory. The laziness +and left associative nature of our grammar hints that we may be dealing with a +thunk stack reminiscient of [foldl](https://wiki.haskell.org/Foldr_Foldl_Foldl%27). +This implies we could fix the issue by evaluating our data strictly with e.g. +[deepseq](https://hackage.haskell.org/package/deepseq). The problem is +unfortunately deeper than this though. Consider the definition of +[InfixL](https://hackage.haskell.org/package/parser-combinators-1.3.0/docs/src/Control.Monad.Combinators.Expr.html#pInfixL)'s +parser: + +```haskell +pInfixL :: MonadPlus m => m (a -> a -> a) -> m a -> a -> m a +pInfixL op p x = do + f <- op + y <- p + let r = f x y + pInfixL op p r <|> return r +``` + +Because this implementation uses the backtracking alternative operator (`<|>`), +we must also hold onto each `<|> return r` in memory "just in case" we need to +use it. Since we are working with a [left-factored](https://www.csd.uwo.ca/~mmorenom/CS447/Lectures/Syntax.html/node9.html) +grammar, we can drop the alternatives and apply strictness with a custom +expression parser: + +```haskell +parseStrict :: Parser Result +parseStrict = term >>= expr + where + expr t = do + op <- M.option Nothing $ Just <$> ops + case op of + Just OpAdd -> nest t asInt EInt EAdd + Just OpSub -> nest t asInt EInt ESub + Just OpAnd -> nest t asBool EBool EAnd + Just OpOr -> nest t asBool EBool EOr + _ -> pure t + + nest + :: forall a + . Result + -> (Result -> Either Text a) + -> (a -> Expr) + -> (Expr -> Expr -> Expr) + -> Parser Result + nest t cast f bin = do + t' <- term + a <- either (fail . unpack) pure do + lhs <- cast t + rhs <- cast t' + toResult $ bin (f lhs) (f rhs) + a `deepseq` expr a + + term = do + p <- M.option Nothing $ Just <$> symbol "(" + if isJust p then (term >>= expr) <* symbol ")" else + RInt <$> integer <|> RBool <$> boolean +``` + +This implementation runs marginally faster and uses a constant amount of memory: + +![parser-initial-100k-heap-hd-strict](/images/tagless-final-parsing/parser-initial-100k-heap-hd-strict.png) + +```{=html} +<.info> +``` +`ARR_WORDS` corresponds to the `ByteString` constructor and is unavoidable so +long as we use megaparsec. +```{=html} + +``` + +To get a better sense of where runtime tends to reside, let's re-run our newly +strict implementation on our 10,000,000 file again alongside a time profile: + +```bash +echo {1..10000000} | sed 's/ / + /g' > input.txt +cabal run --enable-profiling exe:initial-encoding -- \ + input.txt -m strict +RTS -p +``` + +``` +space Text.Megaparsec.Lexer Text/Megaparsec/Lexer.hs:(68,1)-(71,44) 54.9 57.3 +decimal Text.Megaparsec.Char.Lexer Text/Megaparsec/Char/Lexer.hs:363:1-32 24.7 21.4 +lexeme Text.Megaparsec.Lexer Text/Megaparsec/Lexer.hs:87:1-23 12.6 15.7 +ops Parser.Utils src/Parser/Utils.hs:(69,1)-(74,3) 2.6 2.7 +toResult Parser.Initial src/Parser/Initial.hs:(62,1)-(79,29) 2.3 2.2 +run Main app/Main.hs:(42,1)-(54,58) 1.6 0.6 +readTextDevice Data.Text.Internal.IO libraries/text/src/Data/Text/Internal/IO.hs:133:39-64 1.3 0.0 +``` + +Surprisingly, the vast majority of time (roughly 95%) is spent parsing. As such, +we won't worry ourselves about runtime any further. + +### Type Safety + +Let's next explore how we can empower library users with a stricter version of +the `Expr` monotype. In particular, we want to prohibit construction of invalid +expressions. A common strategy is to promote our ADT to a GADT: + +```haskell +data GExpr a where + GInt :: Integer -> GExpr Integer + GBool :: Bool -> GExpr Bool + GAdd :: GExpr Integer -> GExpr Integer -> GExpr Integer + GSub :: GExpr Integer -> GExpr Integer -> GExpr Integer + GAnd :: GExpr Bool -> GExpr Bool -> GExpr Bool + GOr :: GExpr Bool -> GExpr Bool -> GExpr Bool +``` + +By virtue of working with arbitrary, potentially mis-typed input, we must +eventually perform *some* form of type-checking on our input. To get the GADT +representation working requires additional machinery like existential datatypes +and `Rank2Types`. The end user of our library reaps the benefits though, +acquiring a strongly-typed representation of our AST: + +```haskell +data Wrapper = forall a. Show a => Wrapper (GExpr a) + +parseGadt :: Parser Wrapper +parseGadt = term >>= expr + where + ... + nest + :: forall b + . Show b + => Wrapper + -> (forall a. GExpr a -> Either Text (GExpr b)) + -> (b -> GExpr b) + -> (GExpr b -> GExpr b -> GExpr b) + -> Parser Wrapper + nest (Wrapper t) cast f bin = do + Wrapper t' <- term + case (cast t, cast t') of + (Right lhs, Right rhs) -> do + let z = eval $ bin lhs rhs + z `seq` expr (Wrapper $ f z) + (Left e, _) -> fail $ unpack e + (_, Left e) -> fail $ unpack e +``` + +We hide the full details here (refer to the linked Github repository for the +full implementation), but note the changes are minimal outside of the +signatures/data types required to make our existentially quantified type work. +Users of the parser can now unwrap the `Wrapper` type and resume like normal. + +```{=html} +<.warning> +``` +This is an arguable \"improvement\" considering *convenience* takes a dramatic +hit. It is awkward working with the `Wrapper` type. +```{=html} + +``` + +### Expression Problem + +Lastly comes the expression problem, and one that is fundamentally unsolvable +given our current implementation. By nature of (G)ADTs, all data-types are +*closed*. It is not extensible since that would break any static type guarantees +around e.g. pattern matching. To fix this (and other problems still present in +our implementation), we contrast our initial encoding to that of tagless final. + +## Tagless Final + +Let's re-think our GADT example above and refactor it into a typeclass: + +```haskell +class Symantics repr where + eInt :: Integer -> repr Integer + eBool :: Bool -> repr Bool + eAdd :: repr Integer -> repr Integer -> repr Integer + eSub :: repr Integer -> repr Integer -> repr Integer + eAnd :: repr Bool -> repr Bool -> repr Bool + eOr :: repr Bool -> repr Bool -> repr Bool +``` + +This should look familiar. Instances of `GExpr` have been substituted by a +parameter `repr` of kind `* -> *`. Kiselyov describes typeclasses used this way +as a means of defining a class of *interpreters*. An example interpreter could +look like evaluation from before: + +```haskell +newtype Eval a = Eval {runEval :: a} + +instance Symantics Eval where + eInt = Eval + eBool = Eval + eAdd (Eval lhs) (Eval rhs) = Eval (lhs + rhs) + eSub (Eval lhs) (Eval rhs) = Eval (lhs - rhs) + eAnd (Eval lhs) (Eval rhs) = Eval (lhs && rhs) + eOr (Eval lhs) (Eval rhs) = Eval (lhs || rhs) +``` + +To belabor the similarities between the two a bit further, compare the following +two examples side-by-side: + +```haskell +let expr = foldl' eAdd (eInt 0) $ take count (eInt <$> [1..]) +let expr = foldl' EAdd (EInt 0) $ take count (EInt <$> [1..]) +``` + +Outside of some capitalization, the encodings are exactly the same. Considering +these similarities, it's clear type safety is not a concern like it was with +`Expr`. This new paradigm also allows us to write both an implementation that +parallels the memory usage [of above](#resource-usage) as well as a proper +solution to the expression problem. To follow along though first requires a +quick detour into **leibniz equalities**. + +### Leibniz Equality + +Leibniz equality states that two objects are equivalent provided they can be +substituted in all contexts without issue. Consider the definition provided by +the [eq](https://hackage.haskell.org/package/eq) package: + +```haskell +newtype a := b = Refl {subst :: forall c. c a -> c b} +``` + +Here we are saying two types `a` and `b` actually refer to the same type if it +turns out that simultaneously + +```haskell +• Maybe a ~ Maybe b +• Either String a ~ Either String b +• Identity a ~ Identity b +• ... +``` + +and so on, where `~` refers to [equality constraints](https://downloads.haskell.org/~ghc/7.6.3/docs/html/users_guide/equality-constraints.html). +Let's clarify further with an example. Suppose we had types `A` and `B` and +wanted to ensure they were actually the same type. Then there must exist a +function `subst` with signature `c A -> c B` *for all* `c`. What might that +function look like? It turns out there is only one acceptable choice: `id`. + +This might not seem particularly valuable at first glance, but what it does +permit is a means of *proving* equality at the type-level and in a way Haskell's +type system respects. For instance, the following is valid: + +```haskell +>>> import Data.Eq.Type ((:=)(..)) +>>> :set -XTypeOperators + +-- Type-level equality is reflexive. I can prove it since +-- `id` is a suitable candidate for `subst`. +>>> refl = Refl id +>>> :t refl +refl :: b := b + +-- Haskell can verify our types truly are the same. +>>> a = refl :: (Integer := Integer) +>>> a = refl :: (Integer := Bool) + +:7:5: error: + • Couldn't match type ‘Integer’ with ‘Bool’ +``` + +We can also prove less obvious things at the type-level and take advantage of +this later on. As an exercise, consider how you might express the following: + +```haskell +functionEquality + :: a1 := a2 + -> b1 := b2 + -> (a1 -> b1) := (a2 -> b2) +``` + +That is, if `a1` and `a2` are the same types, and `b1` and `b2` are the same +types, then functions of type `a1 -> b1` can equivalently be expressed as +functions from `a2 -> b2`. + +```{=html} +<.accordion header="Answer"> +``` +I suggest reading from the bottom up to better understand why this works. + +```haskell +import Data.Eq.Type ((:=)(..), refl) + +newtype F1 t b a = F1 {runF1 :: t := (a -> b)} +newtype F2 t a b = F2 {runF2 :: t := (a -> b)} + +functionEquality + :: forall a1 a2 b1 b2 + . a1 := a2 + -> b1 := b2 + -> (a1 -> b1) := (a2 -> b2) +functionEquality + (Refl s1) -- s1 :: forall c. c a1 -> c a2 + (Refl s2) -- s2 :: forall c. c b1 -> c b2 + = runF2 -- (a1 -> b1) := (a2 -> b2) + . s2 -- F2 (a1 -> b1) a2 b2 + . F2 -- F2 (a1 -> b1) a2 b1 + . runF1 -- (a1 -> b1) := (a2 -> b1) + . s1 -- F1 (a1 -> b1) b1 a2 + . F1 -- F1 (a1 -> b1) b1 a1 + $ refl -- (a1 -> b1) := (a1 -> b1) +``` +```{=html} + +``` + +### Dynamics + +Within our GADTs example, we introduced data type `Wrapper` to allow us to pass +around GADTs of internally different type (e.g. `GExpr Integer` vs. +`GExpr Bool`) within the same context. We can do something similar via +`Dynamics` in the tagless final world. Though we could use the already available +[dynamics](https://hackage.haskell.org/package/base-4.16.0.0/docs/Data-Dynamic.html) +library, we'll build our own for exploration's sake. + +First, let's create a representation of the types in our grammar: + +```haskell +class Typeable repr where + pInt :: repr Integer + pBool :: repr Bool + +newtype TQ t = TQ {runTQ :: forall repr. Typeable repr => repr t} + +instance Typeable TQ where + pInt = TQ pInt + pBool = TQ pBool +``` + +`TQ` takes advantage of polymorphic constructors to allow us to wrap any +compatible `Typeable` member function and "reinterpret" it as something else. +For example, we can create new `Typeable` instances like so: + +```haskell +newtype AsInt a = AsInt (Maybe (a := Integer)) + +instance Typeable AsInt where + pInt = AsInt (Just refl) + pBool = AsInt Nothing + +newtype AsBool a = AsBool (Maybe (a := Bool)) + +instance Typeable AsBool where + pInt = AsBool Nothing + pBool = AsBool (Just refl) +``` + +We can then use `TQ` to check if something is of the appropriate type: + +```haskell +>>> import Data.Maybe (isJust) +>>> tq = pInt :: TQ Integer +>>> case runTQ tq of AsInt a -> isJust a +True +>>> case runTQ tq of AsBool a -> isJust a +False +``` + +Even more interestingly, we can bundle this type representation alongside a +value of the corresponding type, yielding our desired `Dynamic`: + +```haskell +import qualified Data.Eq.Type as EQ + +data Dynamic repr = forall t. Dynamic (TQ t) (repr t) + +class IsDynamic a where + type' :: forall repr. Typeable repr => repr a + lift' :: forall repr. Symantics repr => a -> repr a + cast' :: forall repr t. TQ t -> Maybe (t := a) + +instance IsDynamic Integer where + type' = pInt + lift' = eInt + cast' (TQ t) = case t of AsInt a -> a + +instance IsDynamic Bool where + type' = pBool + lift' = eBool + cast' (TQ t) = case t of AsBool a -> a + +toDyn :: forall repr a. IsDynamic a => Symantics repr => a -> Dynamic repr +toDyn = Dynamic type' . lift' + +fromDyn :: forall repr a. IsDynamic a => Dynamic repr -> Maybe (repr a) +fromDyn (Dynamic t e) = case t of + (cast' -> r) -> do + r' <- r + pure $ EQ.coerce (EQ.lift r') e +``` + +--- + +```haskell +>>> a = toDyn 5 :: Dynamic Expr +>>> runExpr <$> fromDyn a :: Maybe Integer +Just 5 +``` + +By maintaining a leibniz equality within our `Dynamic` instances (i.e. `AsInt`), +we can internally coerce the wrapped value into the actual type we care about. +With this background information in place, we can finally devise an expression +parser similar to the parser we've written using initial encoding: + +```haskell +parseStrict + :: forall repr + . NFData (Dynamic repr) + => Symantics repr + => Parser (Dynamic repr) +parseStrict = term >>= expr + where + expr :: Dynamic repr -> Parser (Dynamic repr) + expr t = do + op <- M.option Nothing $ Just <$> ops + case op of + Just OpAdd -> nest t eAdd OpAdd + Just OpSub -> nest t eSub OpSub + Just OpAnd -> nest t eAnd OpAnd + Just OpOr -> nest t eOr OpOr + _ -> pure t + + nest + :: forall a + . IsDynamic a + => Dynamic repr + -> (repr a -> repr a -> repr a) + -> Op + -> Parser (Dynamic repr) + nest t bin op = do + t' <- term + case binDyn bin t t' of + Nothing -> fail $ "Invalid operands for `" <> show op <> "`" + Just a -> a `deepseq` expr a + + term :: Parser (Dynamic repr) + term = do + p <- M.option Nothing $ Just <$> symbol "(" + if isJust p then (term >>= expr) <* symbol ")" else + toDyn <$> integer <|> toDyn <$> boolean +``` + +### Interpretations + +So far we've seen very little benefit switching to this strategy despite the +level of complexity this change introduces. Here we'll pose a question that +hopefully makes at least one benefit more obvious. Suppose we wanted to +interpret the parsed expression in two different ways. First, we want a basic +evaluator, and second we want a pretty-printer. In our initial encoding +strategy, the evaluator has already been defined: + +```haskell +eval :: GExpr a -> a +``` + +We say `eval` is one possible interpreter over `GExpr`. It takes in `GExpr`s and +reduces them into literal values. How would a pretty printer work? One candidate +interpreter could look as follows: + +```haskell +pPrint :: GExpr a -> Text +pPrint (GInt e) = pack $ show e +pPrint (GBool e) = pack $ show e +pPrint (GAdd lhs rhs) = "(" <> pPrint lhs <> " + " <> pPrint rhs <> ")" +... +``` + +Unfortunately using this definition requires fundamentally changing how our GADT +parser works. `parseGadt` currently makes certain optimizations based on the +fact only `eval` has been needed so far, reducing the expression as we traverse +the input stream. Generalizing the parser to take in any function of signature +`forall b. (forall a. GExpr a) -> b` (i.e. the signature of some generic +interpreter) would force us to retain memory or accept additional arguments to +make our function especially generic to accommodate.[^2] + +On the other hand, our tagless final approach expects multiple interpretations +from the outset. We can define a `newtype` like + +```haskell +newtype PPrint a = PPrint {runPPrint :: a} + +instance Symantics PPrint where + eInt = PPrint . pack . show + eBool = PPrint . pack . show + eAdd (PPrint lhs) (PPrint rhs) = PPrint $ "(" <> lhs <> " + " <> rhs <> ")" + ... +``` + +and interpret our + +```haskell +parseStrict :: forall repr. Symantics repr => Parser (Dynamic repr) +``` + +`Parser` as a `Dynamic PPrint` instead of a `Dynamic Eval` without losing any +previously acquired gains. + +### Expression Revisited + +There does exist a major caveat with our tagless final interpreters. If owning a +single `Dynamic` instance, how exactly are we able to interpret this in multiple +ways? After all, a `Dynamic` cannot be of type `Dynamic Eval` *and* +`Dynamic PPrint`. What we'd like to be able to do is maintain a generic +`Dynamic repr` and reinterpret it at will. + +One solution comes in the form of another `newtype` around a polymorphic +constructor: + +```haskell +newtype SQ a = SQ {runSQ :: forall repr. Symantics repr => repr a} + +instance Symantics SQ where + eInt e = SQ (eInt e) + eBool e = SQ (eBool e) + eAdd (SQ lhs) (SQ rhs) = SQ (eAdd lhs rhs) + eSub (SQ lhs) (SQ rhs) = SQ (eSub lhs rhs) + eAnd (SQ lhs) (SQ rhs) = SQ (eAnd lhs rhs) + eOr (SQ lhs) (SQ rhs) = SQ (eOr lhs rhs) +``` + +We can then run evaluation and pretty-printing on the same entity: + +```haskell +data Result = RInt Integer | RBool Bool + +runBoth :: Dynamic SQ -> Maybe (Result, Text) +runBoth d = case fromDyn d of + Just (SQ q) -> pure ( case q of Eval a -> RInt a + , case q of PPrint a -> a + ) + Nothing -> case fromDyn d of + Just (SQ q) -> pure ( case q of Eval a -> RBool a + , case q of PPrint a -> a + ) + Nothing -> Nothing +``` + +This has an unintended side effect though. By using `SQ`, we effectively close +our type universe. Suppose we now wanted to extend our `Symantics` type with a +new multiplication operator (`*`). We could do so by writing typeclass: + +```haskell +class MulSymantics repr where + eMul :: repr Integer -> repr Integer -> repr Integer + +instance MulSymantics Eval where + eMul (Eval lhs) (Eval rhs) = Eval (lhs * rhs) + +instance MulSymantics PPrint where + eMul (PPrint lhs) (PPrint rhs) = PPrint $ "(" <> lhs <> " * " <> rhs <> ")" +``` + +```{=html} +<.warning> +``` +Naturally we also need to extend our parser to be aware of the new operator as +well. To avoid diving yet further into the weeds, we do not do that here. +```{=html} + +``` + +But this typeclass is excluded from our `SQ` type. We're forced to write yet +another `SQ`-like wrapper, e.g. + +```haskell +newtype MSQ a = MSQ {runMSQ :: forall repr. MulSymantics repr => repr a} +``` + +just to keep up. This in turn forces us to redefine all functions that operated +on `SQ`. + +#### Copy Symantics + +We can reformulate this more openly, abandoning any sort of `Rank2` constructors +within our `newtype`s by choosing to track multiple representations +simultaneously: + +```haskell +data SCopy repr1 repr2 a = SCopy (repr1 a) (repr2 a) + +instance (Symantics repr1, Symantics repr2) + => Symantics (SCopy repr1 repr2) where + eInt e = SCopy (eInt e) (eInt e) + eBool e = SCopy (eBool e) (eBool e) + eAdd (SCopy a1 a2) (SCopy b1 b2) = SCopy (eAdd a1 b1) (eAdd a2 b2) + eSub (SCopy a1 a2) (SCopy b1 b2) = SCopy (eSub a1 b1) (eSub a2 b2) + eAnd (SCopy a1 a2) (SCopy b1 b2) = SCopy (eAnd a1 b1) (eAnd a2 b2) + eOr (SCopy a1 a2) (SCopy b1 b2) = SCopy (eOr a1 b1) (eOr a2 b2) + +instance (MulSymantics repr1, MulSymantics repr2) + => MulSymantics (SCopy repr1 repr2) where + eMul (SCopy a1 a2) (SCopy b1 b2) = SCopy (eMul a1 b1) (eMul a2 b2) +``` + +As we define new classes of operators on our `Integer` and `Bool` types, we make +`SCopy` an instance of them. We can then "thread" the second representation +throughout our function calls like so: + +```haskell +runEval' + :: forall repr + . Dynamic (SCopy Eval repr) + -> Maybe (Result, Dynamic repr) +runEval' d = case fromDyn d :: Maybe (SCopy Eval repr Integer) of + Just (SCopy (Eval a) r) -> pure (RInt a, Dynamic pInt r) + Nothing -> case fromDyn d :: Maybe (SCopy Eval repr Bool) of + Just (SCopy (Eval a) r) -> pure (RBool a, Dynamic pBool r) + Nothing -> Nothing + +runPPrint' + :: forall repr + . Dynamic (SCopy PPrint repr) + -> Maybe (Text, Dynamic repr) +runPPrint' d = case fromDyn d :: Maybe (SCopy PPrint repr Text) of + Just (SCopy (PPrint a) r) -> pure (a, Dynamic pText r) + Nothing -> Nothing + +runBoth' + :: forall repr + . Dynamic (SCopy Eval (SCopy PPrint repr)) + -> Maybe (Result, Text, Dynamic repr) +runBoth' d = do + (r, d') <- runEval' d + (p, d'') <- runPPrint' d' + pure (r, p, d'') +``` + +Notice each function places a `Dynamic repr` of unknown representation in the +last position of each return tuple. The caller is then able to interpret this +extra `repr` as they wish, composing them in arbitrary ways (e.g. `runBoth'`). + +## Limitations + +The expression problem is only partially solved with our `Dynamic` strategy. If +for instance we wanted to add a new literal type, e.g. a `String`, we would +unfortunately need to append to the `Typeable` and `Dynamic` definitions to +support them. The standard `dynamics` package only allows monomorphic +values so in this sense we are stuck. If only needing to add additional +functionality to the existing set of types though, we can extend at will. + +## Conclusion + +I was initially hoping to extend this post further with a discussion around +explicit sharing as noted [here](/assets/pdf/tagless-final-parsing/kiselyov-embedded.pdf), +but this post is already getting too long. I covered only a portion of the +topics Oleg Kiselyov wrote about, but covered at least the majority of topics +I've so far been exploring in my own personal projects. I will note that the +tagless final approach, while certainly useful, also does add a fair level of +cognitive overhead in my experience. Remembering the details around dynamics +especially is what prompted me to write this post to begin with. + +[^1]: Code here and elsewhere found in the [tagless-final-parsing](https://git.jrpotter.com/blog/tagless-final-parsing) repository. + +[^2]: These "additional arguments" are effectively how the tagless final strategy works. The interface is much cleaner though considering all methods are passed around implicity via the `Symantics` typeclass. diff --git a/lib/portfolio_web/controllers/blog_html/template.html b/lib/portfolio_web/controllers/blog_html/template.html new file mode 100644 index 0000000..599d1ad --- /dev/null +++ b/lib/portfolio_web/controllers/blog_html/template.html @@ -0,0 +1,23 @@ +
+ $if(title)$ +

$title$

+ $endif$ + $if(date)$ +

$date$

+ $endif$ + $if(abstract)$ +
+ $abstract$ +
+ $endif$ + $if(toc)$ + + $endif$ +
♤♠♤♠♤
+ $body$ +
diff --git a/lib/portfolio_web/controllers/error_html.ex b/lib/portfolio_web/controllers/error_html.ex new file mode 100644 index 0000000..b83257f --- /dev/null +++ b/lib/portfolio_web/controllers/error_html.ex @@ -0,0 +1,19 @@ +defmodule PortfolioWeb.ErrorHTML do + use PortfolioWeb, :html + + # If you want to customize your error pages, + # uncomment the embed_templates/1 call below + # and add pages to the error directory: + # + # * lib/portfolio_web/controllers/error_html/404.html.heex + # * lib/portfolio_web/controllers/error_html/500.html.heex + # + # embed_templates "error_html/*" + + # The default is to render a plain text page based on + # the template name. For example, "404.html" becomes + # "Not Found". + def render(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/lib/portfolio_web/controllers/error_json.ex b/lib/portfolio_web/controllers/error_json.ex new file mode 100644 index 0000000..cec3546 --- /dev/null +++ b/lib/portfolio_web/controllers/error_json.ex @@ -0,0 +1,15 @@ +defmodule PortfolioWeb.ErrorJSON do + # If you want to customize a particular status code, + # you may add your own clauses, such as: + # + # def render("500.json", _assigns) do + # %{errors: %{detail: "Internal Server Error"}} + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.json" becomes + # "Not Found". + def render(template, _assigns) do + %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} + end +end diff --git a/lib/portfolio_web/endpoint.ex b/lib/portfolio_web/endpoint.ex new file mode 100644 index 0000000..a277408 --- /dev/null +++ b/lib/portfolio_web/endpoint.ex @@ -0,0 +1,50 @@ +defmodule PortfolioWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :portfolio + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_portfolio_key", + signing_salt: "XPLdDzWD", + same_site: "Lax" + ] + + socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phx.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", + from: :portfolio, + gzip: false, + only: PortfolioWeb.static_paths() + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug PortfolioWeb.Router +end diff --git a/lib/portfolio_web/gettext.ex b/lib/portfolio_web/gettext.ex new file mode 100644 index 0000000..cb1ad5c --- /dev/null +++ b/lib/portfolio_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule PortfolioWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import PortfolioWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :portfolio +end diff --git a/lib/portfolio_web/live/home_live.ex b/lib/portfolio_web/live/home_live.ex new file mode 100644 index 0000000..d80fe81 --- /dev/null +++ b/lib/portfolio_web/live/home_live.ex @@ -0,0 +1,8 @@ +defmodule PortfolioWeb.HomeLive do + use PortfolioWeb, :live_view + + @impl true + def mount(_params, _session, socket) do + {:ok, socket} + end +end diff --git a/lib/portfolio_web/live/home_live.html.heex b/lib/portfolio_web/live/home_live.html.heex new file mode 100644 index 0000000..0cf2e59 --- /dev/null +++ b/lib/portfolio_web/live/home_live.html.heex @@ -0,0 +1,243 @@ +
+
+

Hosted

+

+ The applications listed below are served from + <.link_blank href="https://nixos.org/">NixOS + machines hosted on + <.link_blank href="https://www.digitalocean.com/">Digital Ocean. + Configuration files for each of my machines can be found + <.link_blank href="https://git.jrpotter.com/r/nixos-configuration">here. + If interested in starting a similar hosting solution, consider getting a + $200 credit using my + <.link_blank href="https://m.do.co/c/c65b89434c1b">referral link. +

+ +
+ <.project title="Hide and Seek" href="https://hideandseek.live" date="12 Apr 2024"> + Realtime hide-and-seek application for the town of Fort Collins, built + using + <.link_blank href="https://www.phoenixframework.org/">Phoenix + and + <.link_blank href="https://react.dev/">React. + Group up with friends, designate a hider, and allow the rest of the + group to find the hider in a city-wide search. Use various clues to + narrow down the search space at the cost of potentially giving the + hider means of thwarting the search. + + + <.project title="Notebook" href="https://notebook.jrpotter.com" date="30 Jan 2024"> + A static site generated with + <.link_blank href="https://quartz.jzhao.xyz/">Quartz. + Contains a collection of my transcribed notes, primarily Markdown + managed using + <.link_blank href="https://obsidian.md/">Obsidian. + Hidden within are a collection of + <.link_blank href="https://apps.ankiweb.net/">Anki + flashcards synced using + <.link_blank href="https://github.com/ObsidianToAnki/Obsidian_to_Anki"> + Obsidian_to_Anki + . + + + <.project title="Forgejo" href="https://git.jrpotter.com" date="23 Dec 2023"> + A self-hosted + <.link_blank href="https://forgejo.org/">Forgejo + instance. For the most part, my + <.link_blank href="https://github.com">GitHub + repositories are a mirror of those found here. There do exist a few + repos though that live exclusively on either site. + + + <.project title="BoardWise" href="https://boardwise.gg" date="26 Nov 2023"> + A + <.link_blank href="https://www.phoenixframework.org/">Phoenix- and + <.link_blank href="https://react.dev/">React-based project + that provides an interface for finding chess coaches. This serves as an + alternative to those found on + <.link_blank href="https://lichess.org/coach">Lichess + and + <.link_blank href="https://www.chess.com/coaches">Chess.com. + Based on the + <.link_blank href="https://tailwindui.com/templates/studio">Tailwind Studio + theme. + + + <.project title="Bookshelf" href="https://bookshelf.jrpotter.com" date="05 Feb 2023"> + A collection of books I am actively studying. Usually mathematics or + computer-science based, I aim to prove concepts as I encounter them + using the + <.link_blank href="https://lean-lang.org/">Lean + interactive theorem prover. All proofs are also available in + <.link_blank href="https://www.latex-project.org/">LaTeX. + + + <.project title="Looped" href="" date="04 Apr 2021"> + VP of engineering at Looped, the "Ultimate Virtual Venue". Featured on + <.link_blank href="https://www.forbes.com/sites/ericfuller/2021/01/06/loopedthe-app-helping-fans-mingle-and-meet-artists-personally-during-live-streamed-events"> + Forbes + + and + <.link_blank href="https://techcrunch.com/2021/03/02/looped-raises-7-7m-to-expand-its-interactive-live-event-platform"> + TechCrunch + . + Led development on the Kotlin-based + <.link_blank href="https://play.google.com/store/apps/details?id=com.vipvr.android"> + Android app + + (50K+ downloads, 4.5 star review), the Swift-based iOS app (100K+ + downloads, 4.8 star review), the Vue-based web app, and the + Django-based backend. + +
+
+ +
+

Projects

+

+ Other notable projects I've worked on or am currently working on. +

+
+ <.project + title="Bookshelf Doc Generator" + href="https://git.jrpotter.com/r/bookshelf-doc" + date="14 Dec 2023" + > + A fork of + <.link_blank href="https://github.com/leanprover/doc-gen4">doc-gen4 + tightly coupled to my + <.link_blank href="https://git.jrpotter.com/r/bookshelf">bookshelf + project. This augments the :docs facet to convert LaTeX + files into PDFs and then list them in the generated navbar. + + + <.project + title="NixOS Configuration" + href="https://git.jrpotter.com/r/nixos-configuration" + date="10 Dec 2023" + > + The + <.link_blank href="https://nixos.org">nix + configuration files used to declaratively describe my local and remote + machines. The site you are on now is declared within this project! + + + <.project title="Bootstrap" href="https://git.jrpotter.com/r/bootstrap" date="17 Nov 2023"> + A C-based CLI for initializing projects in a flexible but deterministic + way. Originally motivated to serve as a better alternative to + <.link_blank href="https://github.com/NixOS/templates">Nix flake templates, + bootstrap allows you to provide different parameters to + custom initialization scripts akin to npm init, + django-admin startproject, etc. + + + <.project + title="Anki Synonyms" + href="https://git.jrpotter.com/r/anki-synonyms" + date="02 Jul 2022" + > + An + <.link_blank href="https://apps.ankiweb.net/">Anki + plugin for specifying synonyms within flashcard question and answer + prompts. + + + <.project title="Homesync" href="https://git.jrpotter.com/r/homesync" date="28 Dec 2021"> + An experimental Rust-based project for automatically syncing files + across your desktop to a git repository. Allows upstream and downstream + syncing with a single command, without any need to copy files manually + to and from a git repository. Separately, a daemon can be spawned that + watches files for changes and pushes/pulls them as they happen. + + + <.project title="Postlude" href="https://git.jrpotter.com/r/postlude" date="29 Mar 2020"> + An example of a custom-rolled + <.link_blank href="https://hackage.haskell.org/package/base-4.19.0.0/docs/Prelude.html"> + Prelude + . + Serves as a fairly comprehensive list of imports I found relevant + across the various Haskell projects I worked on as well as a + demonstration on how forwarding imports with Haskell works. + + + <.project title="Vim Join" href="https://git.jrpotter.com/r/vim-join" date="25 Jul 2017"> + A small Vim plugin that joins a number of lines together and then + breaks them again with respect to the textwidth parameter. + This enables re-flowing a set of lines similar in manner to + fold or fmt. + + + <.project + title="Vim Highlight" + href="https://git.jrpotter.com/r/vim-highlight" + date="25 Jul 2017" + > + A small Vim plugin that maintains a custom registry for manipulating + highlights. This registry allows highlighting different keywords + without overriding previous searches. Includes a small snippet for + including the active highlight from within the statusline. + + + <.project title="Pong" href="https://git.jrpotter.com/r/pong" date="01 Oct 2015"> + An implementation of the classic pong video game, written from scratch + on an Artix FPGA using System Verilog. This works on a custom ALU + intended to process an arbitrary MIPS program with modified memory + configuration: .text 0x0000 and .data 0x2000. + A memory mapped IO scheme is used to draw to the monitor and interact + with the keyboard. + + + <.project title="Fifth" href="https://git.jrpotter.com/r/fifth" date="20 Jun 2015"> + A library for parsing various rulesets for cellular automata machines + (CAMs). The parsed CAM is displayed using + <.link_blank href="https://matplotlib.org">matplotlib. + For instance, this library parses ruleset B3/S23 and then + produces a running visualization of Conway's Game of Life. + + + <.project title="Mini Java" href="https://git.jrpotter.com/r/mini-java" date="24 Mar 2015"> + A Java implemention of a subset of Java. Generates code that targets + mJAM, an abstract machine included in the source that supports running + miniJava. In particular, this implementation supports + various primitives, array types, and, to a certain degree, classes. + +
+
+ +
+

Blog

+

+ Long-form writing on topics that I found interesting. +

+ +
+ <.blog title="Effect Systems" href={~p"/blog/effect-systems"} date="20 Mar 2022"> + As I’ve begun exploring the world of so-called algebraic effect systems, + I’ve become increasingly frustrated in the level of documentation around + them. Learning to use them (and moreso understanding how they work) + requires diving into library internals, watching various videos, and + hoping to grok why certain effects aren’t being interpreted the way you + might have hoped. My goal in this post is to address this issue, at least + to some degree, in a focused, pedagogical fashion. + + + <.blog + title="Tagless Final Parsing" + href={~p"/blog/tagless-final-parsing"} + date="25 Dec 2021" + > + In his + <.link_blank href="/files/tagless-final-parsing/kiselyov-interpreters.pdf"> + introductory text + , + Oleg Kiselyov discusses the tagless final strategy for implementing + DSLs. The approach permits leveraging the strong typing provided by + some host language in a performant way. This post combines key thoughts + from a selection of papers and code written on the topic. We conclude + with an implementation of an interpreter for a toy language that runs + quickly, remains strongly-typed, and can be extended without + modification. + +
+
+
diff --git a/lib/portfolio_web/router.ex b/lib/portfolio_web/router.ex new file mode 100644 index 0000000..49876cf --- /dev/null +++ b/lib/portfolio_web/router.ex @@ -0,0 +1,42 @@ +defmodule PortfolioWeb.Router do + use PortfolioWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, html: {PortfolioWeb.Layouts, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", PortfolioWeb do + pipe_through :browser + + live "/", HomeLive + + get "/blog/effect-systems", BlogController, :effect_systems + get "/blog/tagless-final-parsing", BlogController, :tagless_final_parsing + end + + # Enable LiveDashboard and Swoosh mailbox preview in development + if Application.compile_env(:portfolio, :dev_routes) do + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + import Phoenix.LiveDashboard.Router + + scope "/dev" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: PortfolioWeb.Telemetry + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end +end diff --git a/lib/portfolio_web/telemetry.ex b/lib/portfolio_web/telemetry.ex new file mode 100644 index 0000000..e3da5ba --- /dev/null +++ b/lib/portfolio_web/telemetry.ex @@ -0,0 +1,69 @@ +defmodule PortfolioWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.start.system_time", + unit: {:native, :millisecond} + ), + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.start.system_time", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.exception.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.socket_connected.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.channel_joined.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.channel_handled_in.duration", + tags: [:event], + unit: {:native, :millisecond} + ), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {PortfolioWeb, :count_users, []} + ] + end +end diff --git a/menu/archive.md b/menu/archive.md deleted file mode 100644 index f7edbbd..0000000 --- a/menu/archive.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: archive -title: Archive -permalink: /archive ---- - diff --git a/menu/home.md b/menu/home.md deleted file mode 100644 index 32dfdd8..0000000 --- a/menu/home.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -layout: home -title: Home ---- diff --git a/menu/other.md b/menu/other.md deleted file mode 100644 index 03cf38e..0000000 --- a/menu/other.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -layout: home -title: Other ---- diff --git a/menu/projects.md b/menu/projects.md deleted file mode 100644 index 5461747..0000000 --- a/menu/projects.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -layout: home -title: Projects ---- diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..4888be6 --- /dev/null +++ b/mix.exs @@ -0,0 +1,68 @@ +defmodule Portfolio.MixProject do + use Mix.Project + + def project do + [ + app: :portfolio, + version: "0.1.0", + elixir: "~> 1.14", + elixirc_paths: elixirc_paths(Mix.env()), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {Portfolio.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.7.10"}, + {:phoenix_html, "~> 3.3"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 0.20.1"}, + {:floki, ">= 0.30.0", only: :test}, + {:phoenix_live_dashboard, "~> 0.8.2"}, + {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev}, + {:swoosh, "~> 1.3"}, + {:finch, "~> 0.13"}, + {:telemetry_metrics, "~> 0.6"}, + {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 0.20"}, + {:jason, "~> 1.2"}, + {:dns_cluster, "~> 0.1.1"}, + {:plug_cowboy, "~> 2.5"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to install project dependencies and perform other setup tasks, run: + # + # $ mix setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get", "assets.setup", "assets.build"], + "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"], + "assets.build": ["tailwind default", "esbuild default"], + "assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"] + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..bc68351 --- /dev/null +++ b/mix.lock @@ -0,0 +1,37 @@ +%{ + "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, + "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, + "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"}, + "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, + "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, + "expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"}, + "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, + "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, + "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"}, + "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, + "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [: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.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [: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]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.14", "70fa101aa0539e81bed4238777498f6215e9dda3461bdaa067cad6908110c364", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {: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 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "82f6d006c5264f979ed5eb75593d808bbe39020f20df2e78426f4f2d570e2402"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "swoosh": {:hex, :swoosh, "1.16.5", "5742f24c4d081671ebe87d8e7f6595cf75205d7f808cc5d55b09e4598b583413", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.1.0", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2324cf696b09ee52e5e1049dcc77880a11fe618a381e2df1c5ca5d69c380eb0"}, + "tailwind": {:hex, :tailwind, "0.2.2", "9e27288b568ede1d88517e8c61259bc214a12d7eed271e102db4c93fcca9b2cd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [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", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"}, +} diff --git a/other.html b/other.html deleted file mode 100644 index c68c302..0000000 --- a/other.html +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: home -title: Other -pagination: - enabled: true - category: other - indexpage: other ---- diff --git a/priv/gettext/en/LC_MESSAGES/errors.po b/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 0000000..cdec3a1 --- /dev/null +++ b/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,11 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot new file mode 100644 index 0000000..d6f47fa --- /dev/null +++ b/priv/gettext/errors.pot @@ -0,0 +1,10 @@ +## This is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here has no +## effect: edit them in PO (`.po`) files instead. + diff --git a/favicon.ico b/priv/static/favicon.ico similarity index 100% rename from favicon.ico rename to priv/static/favicon.ico diff --git a/priv/static/files/effect-systems/scope.pdf b/priv/static/files/effect-systems/scope.pdf new file mode 100644 index 0000000..6b55d5b Binary files /dev/null and b/priv/static/files/effect-systems/scope.pdf differ diff --git a/priv/static/files/tagless-final-parsing/kiselyov-embedded.pdf b/priv/static/files/tagless-final-parsing/kiselyov-embedded.pdf new file mode 100644 index 0000000..1f038a0 Binary files /dev/null and b/priv/static/files/tagless-final-parsing/kiselyov-embedded.pdf differ diff --git a/priv/static/files/tagless-final-parsing/kiselyov-interpreters.pdf b/priv/static/files/tagless-final-parsing/kiselyov-interpreters.pdf new file mode 100644 index 0000000..7898326 Binary files /dev/null and b/priv/static/files/tagless-final-parsing/kiselyov-interpreters.pdf differ diff --git a/priv/static/images/tagless-final-parsing/parser-initial-100k-heap-hc-single.png b/priv/static/images/tagless-final-parsing/parser-initial-100k-heap-hc-single.png new file mode 100644 index 0000000..5b596c5 Binary files /dev/null and b/priv/static/images/tagless-final-parsing/parser-initial-100k-heap-hc-single.png differ diff --git a/priv/static/images/tagless-final-parsing/parser-initial-100k-heap-hd-strict.png b/priv/static/images/tagless-final-parsing/parser-initial-100k-heap-hd-strict.png new file mode 100644 index 0000000..960f125 Binary files /dev/null and b/priv/static/images/tagless-final-parsing/parser-initial-100k-heap-hd-strict.png differ diff --git a/priv/static/images/tagless-final-parsing/parser-initial-100k-heap-hr-single.png b/priv/static/images/tagless-final-parsing/parser-initial-100k-heap-hr-single.png new file mode 100644 index 0000000..4fc63af Binary files /dev/null and b/priv/static/images/tagless-final-parsing/parser-initial-100k-heap-hr-single.png differ diff --git a/priv/static/robots.txt b/priv/static/robots.txt new file mode 100644 index 0000000..26e06b5 --- /dev/null +++ b/priv/static/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/projects.html b/projects.html deleted file mode 100644 index e1adedd..0000000 --- a/projects.html +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: home -title: Projects -pagination: - enabled: true - category: project - indexpage: projects ---- diff --git a/test/portfolio_web/controllers/error_html_test.exs b/test/portfolio_web/controllers/error_html_test.exs new file mode 100644 index 0000000..29cb038 --- /dev/null +++ b/test/portfolio_web/controllers/error_html_test.exs @@ -0,0 +1,14 @@ +defmodule PortfolioWeb.ErrorHTMLTest do + use PortfolioWeb.ConnCase, async: true + + # Bring render_to_string/4 for testing custom views + import Phoenix.Template + + test "renders 404.html" do + assert render_to_string(PortfolioWeb.ErrorHTML, "404", "html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(PortfolioWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" + end +end diff --git a/test/portfolio_web/controllers/error_json_test.exs b/test/portfolio_web/controllers/error_json_test.exs new file mode 100644 index 0000000..606f6eb --- /dev/null +++ b/test/portfolio_web/controllers/error_json_test.exs @@ -0,0 +1,12 @@ +defmodule PortfolioWeb.ErrorJSONTest do + use PortfolioWeb.ConnCase, async: true + + test "renders 404" do + assert PortfolioWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} + end + + test "renders 500" do + assert PortfolioWeb.ErrorJSON.render("500.json", %{}) == + %{errors: %{detail: "Internal Server Error"}} + end +end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex new file mode 100644 index 0000000..02215d2 --- /dev/null +++ b/test/support/conn_case.ex @@ -0,0 +1,37 @@ +defmodule PortfolioWeb.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use PortfolioWeb.ConnCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # The default endpoint for testing + @endpoint PortfolioWeb.Endpoint + + use PortfolioWeb, :verified_routes + + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import PortfolioWeb.ConnCase + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()