subtree(users/wpcarro): docking briefcase at '24f5a642'
git-subtree-dir: users/wpcarro git-subtree-mainline:464bbcb15cgit-subtree-split:24f5a642afChange-Id: I6105b3762b79126b3488359c95978cadb3efa789
This commit is contained in:
commit
019f8fd211
766 changed files with 175420 additions and 0 deletions
11
users/wpcarro/website/README.md
Normal file
11
users/wpcarro/website/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# wpcarro.dev
|
||||
|
||||
https://wpcarro.dev is my personal website. I expose a few subdomains, one of
|
||||
which you are probably visiting right now, git.wpcarro.dev. Here are some of
|
||||
the others:
|
||||
|
||||
- `blog.wpcarro.dev`: My personal blog
|
||||
- `learn.wpcarro.dev`: Teaching others to code
|
||||
- `sandbox.wpcarro.dev`: Where I deploy some pet projects and code sketches
|
||||
|
||||
Visit https://wpcarro.dev for a sitemap.
|
||||
2
users/wpcarro/website/blog/.envrc
Normal file
2
users/wpcarro/website/blog/.envrc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
source_up
|
||||
use_nix
|
||||
6
users/wpcarro/website/blog/archetypes/default.md
Normal file
6
users/wpcarro/website/blog/archetypes/default.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
||||
25
users/wpcarro/website/blog/config.toml
Normal file
25
users/wpcarro/website/blog/config.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
baseURL = "https://blog.wpcarro.dev"
|
||||
disqusShortname = "blog-wpcarro-dev"
|
||||
languageCode = "en-us"
|
||||
title = "blog.wpcarro.dev"
|
||||
theme = "tailwind"
|
||||
pygmentsCodeFences = true
|
||||
pygmentsUseClasses = true
|
||||
|
||||
[taxonomies]
|
||||
tag = "tags"
|
||||
|
||||
[permalinks]
|
||||
posts = "/posts/:year/:month/:title/"
|
||||
|
||||
[params]
|
||||
author = "William Carroll"
|
||||
description = "Loosely structured streams of consciousness"
|
||||
tagline = "Loosely structured streams of consciousness"
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
contentDir = "content/english"
|
||||
languageCode = "en-us"
|
||||
languageName = "English"
|
||||
weight = 1
|
||||
5
users/wpcarro/website/blog/content/english/caffeine.md
Normal file
5
users/wpcarro/website/blog/content/english/caffeine.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "Caffeine"
|
||||
date: 2020-03-11T22:50:40Z
|
||||
draft: true
|
||||
---
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
---
|
||||
title: "Cell Phone Experiment"
|
||||
date: 2020-04-02T02:02:07Z
|
||||
draft: false
|
||||
---
|
||||
|
||||
### TL;DR
|
||||
|
||||
I will not use my cell phone during March to learn more about how much I depend
|
||||
on it.
|
||||
|
||||
### Explore/Exploit
|
||||
|
||||
Ever since I read Charles Duhigg's book, [The Power of Habit](poh), I try to
|
||||
habituate as many aspects of my life as I can.
|
||||
|
||||
Making my bed every morning is an example of a habit -- so too is flossing at
|
||||
night before bed.
|
||||
|
||||
The *exploit* axis of the [explore/exploit tradeoff](exp-exp) endows habits with
|
||||
their power. Brian Christian and Tom Griffiths explain this concept more clearly
|
||||
than I can in Chapter 2 of their exceptional book, [Algorithms to Live
|
||||
By](algos).
|
||||
|
||||
Habits are powerful, but if I overly exploit an activity, I may settle on a
|
||||
local optimum in lieu of settling on a global optimum; these are the opportunity
|
||||
costs of exploiting (i.e. habits) versus exploring (i.e. spontaneity).
|
||||
|
||||
But what if it was possible to habituate exploration?
|
||||
|
||||
### Monthly challenges
|
||||
|
||||
Every month since October 2018, I challenge myself to try something new. In the
|
||||
past, monthly challenges have been things like:
|
||||
- sign up and take Brazilian Jiu Jitsu classes
|
||||
- buy a guitar and learn [Freight Train](https://www.youtube.com/watch?v=IUK8emiWabU)
|
||||
- study Italian
|
||||
- learn a handstand
|
||||
|
||||
Typically for an activity to qualify as a challenge, I must spend *at least
|
||||
fifteen minutes* working on it *at least five days* each week.
|
||||
|
||||
This month (i.e. March) I'm challenging myself to avoid using my cell phone.
|
||||
|
||||
My parents gave me a cell phone when when I was a freshman in High School; I was
|
||||
14 years old. I am now 28, so I have been using a cell phone semi-daily for over
|
||||
a decade.
|
||||
|
||||
While I enjoy the convenience that my cell phone provides me, I am curious to
|
||||
suspend my usage to more clearly understand how much I depend on it...
|
||||
|
||||
### April
|
||||
|
||||
Now it is early April, and I completed March's challenge. So how was it?
|
||||
|
||||
Below I outline the parts of using a cell phone that I missed and the parts that
|
||||
I surprisingly did not miss. I will also mention the two times that I used my
|
||||
cell phone and why.
|
||||
|
||||
The first three things that I missed all relate to time.
|
||||
|
||||
#### Timekeeping
|
||||
|
||||
On the first day I realized that unless I was near a computer, I did not know
|
||||
what time it was.
|
||||
|
||||
I exclusively use my cell phone as my watch; I do not wear a watch. To adapt, I
|
||||
started looking for clocks around my office and while I was taking public
|
||||
transportation. Thankfully London posts the current time on the digital train
|
||||
schedules. This oriented me while I was traveling, which was also when I needed
|
||||
to know the time the most.
|
||||
|
||||
Most of the month, however, I never precisely knew what time it was.
|
||||
|
||||
#### Alarm clocks
|
||||
|
||||
While I anticipated living without an alarm clock prior to the experiment, I
|
||||
decided against buying a substitute. Prior to this month, I theorized that
|
||||
morning alarms probably disrupt the quality of my sleep. If I'm tired, shouldn't
|
||||
I keep sleeping?
|
||||
|
||||
As the month progressed and my 24 hour day morphed into a 25 hour day, I learned
|
||||
that I would prefer waking up at a set time every day and synchronize my
|
||||
schedule with the rest of my timezone.
|
||||
|
||||
I am still unsure if alarm clocks are helpful in the long-term. I would have
|
||||
slept with the curtains drawn to allow the morning sun to wake me
|
||||
up. Unfortunately, I live on the ground floor nearby a brightly shining street
|
||||
lamp that spills into my bedroom.
|
||||
|
||||
If I lived somewhere more remote (perhaps even a suburb would do) I would like
|
||||
to repeat an experiment where I live for a month without an alarm clock.
|
||||
|
||||
For now, I must return to the Temple of Chronology and supplicate until Father
|
||||
Time restores my sanity.
|
||||
|
||||
#### Timers
|
||||
|
||||
Using timers motivates me to do a bunch of short tasks like cleaning my flat for
|
||||
fifteen minutes, stretching, or reading before bed. Thankfully, I already owned
|
||||
a physical timer that I keep in my kitchen. This replaced the timer on my phone
|
||||
without disrupting my routine.
|
||||
|
||||
#### Maps
|
||||
|
||||
Speaking of being disoriented, what about living without maps software? On the
|
||||
few occasions where I traveled somewhere that was unfamiliar to me, I had to
|
||||
memorize the directions from my computer before I departed.
|
||||
|
||||
At least I didn't need to visit gas stations or museums to buy trifold tourist
|
||||
maps...
|
||||
|
||||
I once left my office mistakenly assuming that I would download the directions
|
||||
to my destination while commuting. As I awaited the office elevator, I realized
|
||||
that I had no clue where I was heading.
|
||||
|
||||
Thankfully I wasn't far from the safety, comfort, and familiarity of my desktop
|
||||
computer -- with its fatty WiFi connection. In no time I was studying Google
|
||||
Maps in my web browser and memorizing the directions.
|
||||
|
||||
Overall this was hardly an inconvenience, and I think I even enjoyed
|
||||
stress-testing my memory: a job that I so often outsource to hardware.
|
||||
|
||||
#### Rendezvouses
|
||||
|
||||
A couple of times I met friends in various parts of the city. Organizing these
|
||||
particular rendezvouses was a novel (read: anachronistic) experience. For all
|
||||
you young whippersnappers reading, take out your stone tablets and chisels. I'm
|
||||
going to explain how this works:
|
||||
|
||||
First I would tell my friends where and when to meet me. I emphasized that I
|
||||
would be quite helpless to any changes they might make to the plans once I began
|
||||
commuting, which made the commitments unusually more binding.
|
||||
|
||||
On one occasion my friend -- who is characteristically prompt, and even chides
|
||||
me for when I'm late -- was twenty minutes late for our engagement. My friend is
|
||||
German, so I figured I should do my civic duty of alerting the German embassy
|
||||
that my friend had broken German code, is obscenely late, and should therefore
|
||||
hand-in his passport and renounce his citizenship. After awhile my conscience
|
||||
advised me to reconsider.
|
||||
|
||||
It was fortunate for both of us that I did not fully understand how late he was.
|
||||
Remember: I didn't know what time it was.
|
||||
|
||||
I decided this would be a useful opportunity to test my patience, so I loitered
|
||||
for twenty minutes outside of our meeting point. He couldn't text me to tell me
|
||||
that he was late. I couldn't listen to music, call family or friends, or partake
|
||||
in any of the other rituals that modern-day loiterers observe to pass the
|
||||
time. In the end he showed up, and it was scarcely a big deal.
|
||||
|
||||
This experience made me wonder what the policy for abandoning plans is when
|
||||
someone is running late. Before smart phones, how long did people wait? Maybe
|
||||
the proper etiquette is to wait long enough for you to absolve yourself of the
|
||||
guilt of flaking in the unlikely event that your friend arrives shortly after
|
||||
you leave.
|
||||
|
||||
So... thirty minutes? I'll call my grandma tomorrow and ask her.
|
||||
|
||||
#### Boredom
|
||||
|
||||
My phone couldn't entertain me while I queued at the grocery store. Same too
|
||||
when I commuted.
|
||||
|
||||
I also found myself listening to less music than I usually do. I decided to read
|
||||
to occupy the void when I could; this helped me progress towards completing this
|
||||
year's [GoodReads challenge][gr-annual].
|
||||
|
||||
### Cheating
|
||||
|
||||
I used my phone twice during March.
|
||||
|
||||
1. Once to use my bank's mobile app to internationally transfer money from my
|
||||
U.K. account to my U.S. account. I could have used [TransferWise's][tw]
|
||||
website, but I didn't.
|
||||
2. Another time I used my phone to take pictures of an item that I wanted to
|
||||
sell on [CraigsList][cl]. I could have and perhaps should have used my laptop's
|
||||
webcam, but at the time, I didn't want to. I am accustomed to using my phone
|
||||
to take pictures, and I wanted to sell something.
|
||||
|
||||
In both of these cases, prior habits eroded my resolve to stay the course. These
|
||||
are useful reminders that habits don't distinguish between helpful and hurtful;
|
||||
they just exist.
|
||||
|
||||
In total I would estimate that I spent somewhere around fifteen minutes using
|
||||
my phone in March. While not perfect:
|
||||
|
||||
> Better a diamond with a flaw than a pebble without (Confucius)
|
||||
|
||||
### Substitution = Dilution
|
||||
|
||||
While the explicit goal of this challenge was to avoid using my cell phone for a
|
||||
month, the implicit goal was to disengage from many of the
|
||||
[nonessential][essentialism] activities that compete for my attention.
|
||||
|
||||
There were some activities that I didn't miss while living without a cell
|
||||
phone. This wasn't because I don't value these activities, but rather because I
|
||||
can adequately replace them with alternatives.
|
||||
|
||||
For texting and making phone calls, I used [Telegram][wtf-telegram]. Telegram
|
||||
helped me sustain a healthy relationship with my girlfriend while still honoring
|
||||
the constraints of the challenge.
|
||||
|
||||
While I appreciated the convenience Telegram provided, I felt that I remained
|
||||
about as [available][wtf-availability] during March as I was in February. If I
|
||||
ever experiment with drastically reducing my availability, I will be more
|
||||
explicit about my objectives.
|
||||
|
||||
### Distraction displacement (whack-a-mole)
|
||||
|
||||
Because cell phones and other electronics have conditioned my behavior, I
|
||||
habitually avoid boredom and seek entertainment. On its face this may not sound
|
||||
like a harmful practice. My generation drills the aphorism "you only live once",
|
||||
suggesting that we may want to embrace a Hedonistic lifestyle.
|
||||
|
||||
Hedonism may or may not be a wise way to play the game of Life. All I know is
|
||||
that living a life in which I am often stimulated but proportionately distracted
|
||||
appeals increasingly less to me as time progresses.
|
||||
|
||||
During March I noticed that once I freed my attention from sending/receiving
|
||||
texts, my brain quickly reassigned my attention to maintaining a vigil over the
|
||||
other social media outposts that I maintain.
|
||||
|
||||
I should also admit that I habitually checked Telegram now that it served as my
|
||||
new cell phone. Didn't see that coming...
|
||||
|
||||
In another case, once I discovered that I could use Instagram in a web browser
|
||||
instead of on my phone, I filled my newfound time and attention on
|
||||
[Instagram.com][ig] (don't click!): displacing the time that I spent on an app
|
||||
on my phone to time that I spent on a website in a web browser.
|
||||
|
||||
Holy whack-a-mole!
|
||||
|
||||
Halfway through the month, I wrote a [program to block websites][url-blocker] on
|
||||
my computer. Surprisingly this worked and forced me to more deliberately fill
|
||||
this hard-fought, foreign time with other activities.
|
||||
|
||||
### Easy come, easy go?
|
||||
|
||||
As the saying for making friends goes, "easy come, easy go", implying that
|
||||
friendships that you easily form can just as easily be destroyed.
|
||||
|
||||
Habits invert this creation/destruction relationship. In my experience "easy
|
||||
come" implies "difficult to go".
|
||||
|
||||
For example, I could easily form the habit of eating chocolate around 15:00 at
|
||||
work; curbing this habit would require more effort. When I compare this to the
|
||||
difficulty I experienced habituating a meditation practice, and how easily I
|
||||
can dislodge my meditation practice, it seems to me that the laws of habits
|
||||
dictate "easy come, difficult go; difficult come, easy go".
|
||||
|
||||
I suspect that while my cravings for using a cell phone have temporarily ceased,
|
||||
they will return shortly after I start using my cell phone. And as if nothing
|
||||
happened, I return to where I was at the end of February just before I decided
|
||||
to curb my cell phone usage.
|
||||
|
||||
Because of this, I'm planning on keeping my cell phone in my closet where I
|
||||
stored it during the month of March. As noted, enough substitutes exist for me
|
||||
to live a mostly normal life: one where I am not unnecessarily straining the
|
||||
relationships of my friends and my family. After all these are the people who
|
||||
matter most to me and those who drive me to explore new ways to improve.
|
||||
|
||||
I recognize that the "self" in self-experimentation is a misnomer. Can you truly
|
||||
conduct an [N of 1 trial][nof1]? My decisions impact the people in my life, and
|
||||
I want to thank everyone who tolerates my eccentric and oftentimes annoying
|
||||
experimentation.
|
||||
|
||||
Thank you for reading.
|
||||
|
||||
[pod]: https://www.goodreads.com/book/show/12609433-the-power-of-habit
|
||||
[exp-exp]: https://en.wikipedia.org/wiki/Multi-armed_bandit
|
||||
[algos]: https://www.goodreads.com/book/show/25666050-algorithms-to-live-by
|
||||
[gr-annual]: https://www.goodreads.com/user_challenges/19737920
|
||||
[cl]: http://craigslist.com
|
||||
[tw]: https://transferwise.com
|
||||
[url-blocker]: https://github.com/wpcarro/url-blocker
|
||||
[wtf-telegram]: https://telegram.org
|
||||
[wtf-availability]: https://landing.google.com/sre/sre-book/chapters/availability-table
|
||||
[essentialism]: https://www.goodreads.com/book/show/18077875-essentialism
|
||||
[ig]: https://instagram.com
|
||||
[nof1]: https://en.wikipedia.org/wiki/N_of_1_trial
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
title: "Lets Learn Nix Caching"
|
||||
date: 2020-03-17T18:05:38Z
|
||||
draft: true
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
1. I use `NixOS/nixpkgs-channels` instead of `NixOS/nixpkgs` and avoid
|
||||
`nix-channel`.
|
||||
|
||||
## More information
|
||||
|
||||
- By default the Nix package manager uses cache.nixos.org as a binary cache.
|
||||
- Visit status.nixos.org
|
||||
- `git clone git@github.com:NixOS/nixpkgs-channels` instead of
|
||||
`NixOS/nixpkgs`. The former mirrors the latter and uses Git branches to track
|
||||
the published channels.
|
||||
|
||||
## What is a Nix channel
|
||||
|
||||
If you run...
|
||||
|
||||
```shell
|
||||
$ git clone git@github.com:NixOS/nixpkgs ~/nixpkgs
|
||||
$ export NIX_PATH="nixpkgs=$(realpath ~/nixpkgs)"
|
||||
```
|
||||
|
||||
One benefit to cloning nixpkgs is that you can browse the source code on your
|
||||
machine using tools like `git` and `emacs`. You can also experimentally patch
|
||||
and test Nix code this way.
|
||||
|
||||
If any of the above appeals to you, clone `nixpkgs-channels` instead.
|
||||
|
||||
The Nix maintainers build and test the commits from `nixpkgs` using Hydra. Tests
|
||||
include reproducibility tests, etc.
|
||||
|
||||
Various channels have different verification phases.
|
||||
|
||||
The cache at cache.nixos.org is populate the cache at cache.nixos.org.
|
||||
|
||||
You want to increase the likelihood that you are hitting this cache. For
|
||||
example, `google-chrome` takes hours to build.
|
||||
|
||||
## What is a binary cache?
|
||||
|
||||
## What is Hydra (Nix CI)?
|
||||
|
||||
## What is Cachix?
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
---
|
||||
title: "Lets Learn Nix: Reproducibility"
|
||||
date: 2020-03-17T12:06:47Z
|
||||
draft: true
|
||||
---
|
||||
|
||||
I am dedicating this page to defining and disambiguating some terminology. I
|
||||
think it is important to use these terms precisely, so it may be worthwhile to
|
||||
memorize these definitions and ensure that you are clarifying the discourse
|
||||
rather than muddying it.
|
||||
|
||||
## Terms
|
||||
|
||||
- repeatable build:
|
||||
- reproducible build:
|
||||
- deterministic build:
|
||||
- pure function:
|
||||
- impure function:
|
||||
- idempotent function:
|
||||
|
||||
TODO(wpcarro): Consistently and deliberately use reproducible and
|
||||
deterministic.
|
||||
|
||||
## Repeatable vs. Reproducible
|
||||
|
||||
Is NixOS reproducible? Visit [@grhmc][who-grhmc]'s website,
|
||||
[r13y.com](https://r13y.com), to find out.
|
||||
|
||||
At the time of this writing, 1519 of 1568 (i.e. 96.9%) of the paths in the
|
||||
`nixos.iso_minimal.x86_64-linux` installation image are reproducible.
|
||||
|
||||
## What hinders reproducibility?
|
||||
|
||||
Timestamps.
|
||||
|
||||
If package A encodes a timestamp into its build artifact, then we can
|
||||
demonstrate that package A is *not reproducible* simply by building it at two
|
||||
different times and doing a byte-for-byte comparison of the build artifacts.
|
||||
|
||||
## Does Nix protect developers against non-determinism
|
||||
|
||||
Yes. But not entirely. How?
|
||||
|
||||
## Deterministic Nix derivation
|
||||
|
||||
```nix
|
||||
{ pkgs ? import <nixpkgs> {}, ... }:
|
||||
|
||||
with pkgs;
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "reproducible";
|
||||
phases = [ "buildPhase" ];
|
||||
buildPhase = "echo reproducible >$out";
|
||||
}
|
||||
```
|
||||
|
||||
## Non-deterministic Nix derivation
|
||||
|
||||
We can introduce some non-determinism into our build using the `date` function.
|
||||
|
||||
```nix
|
||||
# file: /tmp/test.nix
|
||||
{ pkgs ? import <nixpkgs> {}, ... }:
|
||||
|
||||
with pkgs;
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "non-reproducible";
|
||||
phases = [ "buildPhase" ];
|
||||
buildPhase = "date >$out";
|
||||
}
|
||||
```
|
||||
|
||||
Then run...
|
||||
|
||||
```shell
|
||||
$ nix-build /tmp/test.nix
|
||||
$ nix-build /tmp/test.nix --check --keep-failed
|
||||
```
|
||||
|
||||
## How do you test reproducibility?
|
||||
|
||||
We can use `cmp` to compare files byte-for-byte. The following comparison should
|
||||
fail:
|
||||
|
||||
```shell
|
||||
$ echo foo >/tmp/a
|
||||
$ echo bar >/tmp/b
|
||||
$ cmp --silent /tmp/{a,b}
|
||||
$ echo $?
|
||||
```
|
||||
|
||||
And the following comparison should succeed:
|
||||
|
||||
```shell
|
||||
$ echo hello >/tmp/a
|
||||
$ echo hello >/tmp/b
|
||||
$ cmp --silent /tmp/{a,b}
|
||||
$ echo $?
|
||||
```
|
||||
|
||||
## Reproducible vs. deterministic
|
||||
|
||||
Reproducible builds *are* deterministic builds and deterministic build
|
||||
|
||||
## Deterministic, Reproducible, Pure, Idempotent, oh my
|
||||
|
||||
- A pure function has no side-effects.
|
||||
|
||||
- An idempotent function can be executed more than once with the same arguments
|
||||
without altering the side-effects.
|
||||
|
||||
- A deterministic function ensures that
|
||||
|
||||
## Deterministic vs. Reproducible
|
||||
|
||||
I can check if a build is reproducible using [these tools][wtf-repro-tools].
|
||||
|
||||
[wtf-repro-tools]: https://reproducible-builds.org/tools/
|
||||
[who-grhmc]: https://twitter.com/grhmc
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
---
|
||||
title: "Let's Learn Nix: Dotfiles"
|
||||
date: 2020-03-13T22:23:02Z
|
||||
draft: true
|
||||
---
|
||||
|
||||
## Let's Learn Nix: Dotfiles
|
||||
|
||||
### Dependencies
|
||||
|
||||
Speaking of dependencies, here's what you should know before reading this tutorial.
|
||||
|
||||
- Basic Nix syntax: Nix 1p
|
||||
|
||||
What version of Nix are we using? What version of `<nixpkgs>` are we using? What
|
||||
operating system are we using? So many variables...
|
||||
|
||||
Cartesian product of all possibilities...
|
||||
|
||||
TODO(wpcarro): Create a graphic of the options.
|
||||
|
||||
### The problems of dotfiles
|
||||
|
||||
How do you manage your dependencies?
|
||||
|
||||
You can use `stow` to install the dotfiles.
|
||||
|
||||
### home-manager
|
||||
|
||||
What we are going to write is most likely less preferable to the following
|
||||
alternatives:
|
||||
- using Nix home-manager
|
||||
- committing your `.gitconfig` into your
|
||||
|
||||
In the next tutorial, we will use [home-manager][wtf-home-mgr] to replace the
|
||||
functionality that we wrote.
|
||||
|
||||
So why bother completing this?
|
||||
|
||||
### Let's begin
|
||||
|
||||
Welcome to the first tutorial in the [Let's Learn Nix][wtf-lln] series. Today we
|
||||
are going to create a Nix derivation for one of your dotfiles.
|
||||
|
||||
"Dotfiles" refers to a user's collection of configuration files. Typically these
|
||||
files look like:
|
||||
- `.vimrc`
|
||||
- `.xsessionrc`
|
||||
- `.bashrc`
|
||||
|
||||
The leading "dot" at the beginning gives dotfiles their name.
|
||||
|
||||
You probably have amassed a collection of dotfiles whether or not you are
|
||||
aware. For example, if you use [git][wtf-git], the file `~/.gitconfig` should
|
||||
exist on your machine. You can verify this with:
|
||||
|
||||
```shell
|
||||
$ stat ~/.gitconfig
|
||||
```
|
||||
|
||||
When I was first learning `git`, I learned to configure it using commands I
|
||||
found in books and tutorials that often looked like:
|
||||
|
||||
```shell
|
||||
$ git config user.email
|
||||
```
|
||||
|
||||
The `~/.gitconfig` file on your machine may look something like this:
|
||||
|
||||
```.gitconfig
|
||||
[user]
|
||||
name = John Cleese
|
||||
email = john@flying-circus.com
|
||||
username = jcleese
|
||||
[core]
|
||||
editor = emacs
|
||||
[web]
|
||||
browser = google-chrome
|
||||
[rerere]
|
||||
enabled = 1
|
||||
autoupdate = 1
|
||||
[push]
|
||||
default = matching
|
||||
[color]
|
||||
ui = auto
|
||||
[alias]
|
||||
a = add --all
|
||||
ai = add -i
|
||||
b = branch
|
||||
cl = clone
|
||||
cp = cherry-pick
|
||||
d = diff
|
||||
fo = fetch origin
|
||||
lg = log --oneline --graph --decorate
|
||||
ps = push
|
||||
pb = pull --rebase
|
||||
s = status
|
||||
```
|
||||
|
||||
As I ran increasingly more `git config` commands to configure my `git`
|
||||
preferences, the size of my `.gitconfig` increased, and the less likely I was to
|
||||
remember which options I set to which values.
|
||||
|
||||
Thankfully a coworker at the time, Ryan ([@rschmukler][who-ryan]), told me that
|
||||
he version-controlled his `.gitconfig` file along with his other configuration
|
||||
files (e.g. `.vimrc`) in a repository he called "dotfiles".
|
||||
|
||||
Version-controlling your dotfiles improves upon a workflow where you have a
|
||||
variety of configuration files scattered around your machine.
|
||||
|
||||
If you look at the above `.gitconfig`, can you spot the dependencies?
|
||||
|
||||
We explicitly depend `emacs` and `google-chrome`. We also *implicitly* depend on
|
||||
`git`: there is not much value of having a `.gitconfig` file if you also do not
|
||||
have `git` installed on your machine.
|
||||
|
||||
Dependencies:
|
||||
- `emacs`
|
||||
- `google-chrome`
|
||||
|
||||
Let's use Nix to generate this `.gitconfig` file. Here is what I would like our
|
||||
API to be:
|
||||
|
||||
Let's create a file `gitconfig.nix` and build our function section-by-section:
|
||||
|
||||
TODO(wpcarro): Link to sections here
|
||||
- options.user
|
||||
- options.core
|
||||
- options.web
|
||||
- options.rerere
|
||||
- options.push
|
||||
- options.color
|
||||
- options.alias
|
||||
|
||||
```shell
|
||||
$ touch gitconfig.nix
|
||||
```
|
||||
|
||||
### options.user
|
||||
|
||||
```haskell
|
||||
AttrSet -> String
|
||||
```
|
||||
|
||||
```nix
|
||||
user = {
|
||||
name = "John Cleese";
|
||||
email = "john@flying-circus.com";
|
||||
username = "jcleese";
|
||||
};
|
||||
```
|
||||
|
||||
```.gitconfig
|
||||
[user]
|
||||
name = John Cleese
|
||||
email = john@flying-circus.com
|
||||
username = jcleese
|
||||
```
|
||||
|
||||
### options.core
|
||||
|
||||
```nix
|
||||
core = {
|
||||
editor = "${pkgs.emacs}/bin/emacs";
|
||||
};
|
||||
```
|
||||
|
||||
```.gitconfig
|
||||
[core]
|
||||
editor = /nix/store/<hash>-emacs-<version>/bin/emacs
|
||||
```
|
||||
|
||||
### options.web
|
||||
|
||||
```nix
|
||||
web.browser = "${pkgs.google-chrome}/bin/google-chrome";
|
||||
```
|
||||
|
||||
```.gitconfig
|
||||
[web]
|
||||
browser = /nix/store/<hash>-google-chrome-<version>/bin/google-chrome
|
||||
```
|
||||
|
||||
### options.rerere
|
||||
|
||||
```nix
|
||||
rerere = {
|
||||
enabled = true;
|
||||
autoupdate = true;
|
||||
};
|
||||
```
|
||||
|
||||
```.gitconfig
|
||||
[rerere]
|
||||
enabled = 1
|
||||
autoupdate = 1
|
||||
```
|
||||
|
||||
### options.push
|
||||
|
||||
```nix
|
||||
push.default = "matching";
|
||||
```
|
||||
|
||||
```.gitconfig
|
||||
[push]
|
||||
default = matching
|
||||
```
|
||||
|
||||
### options.color
|
||||
|
||||
```nix
|
||||
color.ui = "auto";
|
||||
```
|
||||
|
||||
```.gitconfig
|
||||
[color]
|
||||
ui = auto
|
||||
```
|
||||
|
||||
We need to define a function named `gitconfig` that creates a Nix [derivation][wtf-derivation]:
|
||||
|
||||
```nix
|
||||
# file: gitconfig.nix
|
||||
let
|
||||
# Import the <nixpkgs> package repository.
|
||||
pkgs = import <nixpkgs> {};
|
||||
|
||||
# Stringify the attribute set, `xs`, as a multilined string formatted as "<key> = <value>".
|
||||
# See attrsets.nix for more functions that work with attribute sets.
|
||||
encodeAttrSet = xs: lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") xs);
|
||||
|
||||
# Define out function name `gitconfig` that accepts an `options` argument.
|
||||
gitconfig = options: pkgs.stdenv.mkDerivation {
|
||||
# The gitconfig file that Nix builds will be located /nix/store/some-hash-gitconfig.
|
||||
name = "gitconfig";
|
||||
src = pkgs.writeTextFile ".gitconfig" ''
|
||||
[user]
|
||||
name = ${options.user.name}
|
||||
email = ${options.user.email}
|
||||
username = ${options.user.username}
|
||||
[core]
|
||||
editor = ${options.core.editor}
|
||||
[web]
|
||||
editor = ${options.web.browser}
|
||||
[rerere]
|
||||
enabled = ${if options.rerere.enabled "1" else "0"}
|
||||
autoupdate = ${if options.rerere.autoupdate "1" else "0"}
|
||||
[push]
|
||||
default = ${options.push.default}
|
||||
[color]
|
||||
ui = ${options.color.ui}
|
||||
[alias]
|
||||
${encodeAttrSet options.aliases}
|
||||
'';
|
||||
buildPhase = ''
|
||||
${pkgs.coreutils}/bin/cp $src $out
|
||||
'';
|
||||
installPhase = ''
|
||||
${pkgs.coreutils}/bin/ln -s $out ~/.gitconfig
|
||||
'';
|
||||
};
|
||||
} in gitconfig {
|
||||
user = {
|
||||
name = "John Cleese";
|
||||
email = "john@flying-circus.com";
|
||||
username = "jcleese";
|
||||
};
|
||||
core = {
|
||||
editor = "${pkgs.emacs}/bin/emacs";
|
||||
};
|
||||
web.browser = "${pkgs.google-chrome}/bin/google-chrome";
|
||||
rerere = {
|
||||
enabled = true;
|
||||
autoupdate = true;
|
||||
};
|
||||
push.default = "matching";
|
||||
color.ui = "auto";
|
||||
aliases = {
|
||||
a = "add --all";
|
||||
ai = "add -i";
|
||||
b = "branch";
|
||||
cl = "clone";
|
||||
cp = "cherry-pick";
|
||||
d = "diff";
|
||||
fo = "fetch origin";
|
||||
lg = "log --oneline --graph --decorate";
|
||||
ps = "push";
|
||||
pb = "pull --rebase";
|
||||
s = "status";
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### options.alias
|
||||
|
||||
We want to write a function that accepts an attribute set and returns a
|
||||
string. While Nix is a dynamically typed programming language, thinking in types
|
||||
helps me clarify what I'm trying to write.
|
||||
|
||||
```haskell
|
||||
encodeAttrSet :: AttrSet -> String
|
||||
```
|
||||
|
||||
I prefer using a Haskell-inspired syntax for describing type signatures. Even if
|
||||
you haven't written Haskell before, you may find the syntax intuitive.
|
||||
|
||||
Here is a non comprehensive, but demonstrative list of example type signatures:
|
||||
- `[String]`: A list of strings (i.e. `[ "cogito" "ergo" "sum" ]`)
|
||||
- `AttrSet`: A nix attribute set (i.e. `{ name = "John Cleese"; age = 80; }`).
|
||||
- `add :: Integer -> Integer -> Integer`: A function named `add` that accepts
|
||||
two integers and returns an integer.
|
||||
|
||||
Specifically, we want to make sure that when we call:
|
||||
|
||||
```nix
|
||||
encodeAttrSet {
|
||||
a = "add --all";
|
||||
b = "branch";
|
||||
}
|
||||
```
|
||||
|
||||
...it returns a string that looks like this:
|
||||
|
||||
```.gitconfig
|
||||
a = "add --all"
|
||||
b = "branch"
|
||||
```
|
||||
|
||||
|
||||
TODO(wpcarro): @tazjin's nix-1p mentions this. Link to it.
|
||||
Nix has useful functions scattered all over the place:
|
||||
- `lib.nix`
|
||||
- `list.nix`
|
||||
- `lib.attrSet`
|
||||
|
||||
But I cannot recall exactly which functions we will need to write
|
||||
`encodeAttrSet`. In these cases, I do the following:
|
||||
1. Run `nix repl`.
|
||||
2. Browse the Nix source code.
|
||||
|
||||
Google "nix attribute sets" and find the Github link to `attrsets.nix`.
|
||||
|
||||
You should consider repeating this search but instead of searching for
|
||||
"attribute sets" search for "lists" and "strings". That is how I found the
|
||||
functions needed to write `encodeAttrSet`. Let's return to our `nix repl`.
|
||||
|
||||
Load the nixpkgs set:
|
||||
|
||||
```nix
|
||||
nix-repl> :l <nixpkgs>
|
||||
Added 11484 variables.
|
||||
```
|
||||
|
||||
Define a test input called `attrs`:
|
||||
|
||||
```nix
|
||||
nix-repl> attrs = { fname = "John"; lname = "Cleese"; }
|
||||
```
|
||||
|
||||
Map the attribute set into `[String]` using `lib.mapAttrsToList`:
|
||||
|
||||
```nix
|
||||
nix-repl> lib.mapAttrsToList (k: v: "${k} = ${toString v}") attrs
|
||||
[ "fname = John" "lname = Cleese" ]
|
||||
```
|
||||
|
||||
Now join the `[String]` together using `lib.concatStringsSep`:
|
||||
|
||||
```nix
|
||||
nix-repl> lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") attrs)
|
||||
"fname = John\nlname = Cleese"
|
||||
```
|
||||
|
||||
Now let's use this to define our function `encodeAttrSet`:
|
||||
|
||||
```nix
|
||||
# file: gitconfig.nix
|
||||
encodeAttrSet = xs: lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} = ${v}") xs);
|
||||
```
|
||||
|
||||
### Using nixpkgs search
|
||||
|
||||
[Nixpkgs search][wtf-nixpkgs-search].
|
||||
|
||||
### Conclusion
|
||||
|
||||
We learned how to help ourselves.
|
||||
|
||||
- Where does `emacs` exist? What about `google-chrome`? [nixpkgs search][wtf-nixpkgs-search]
|
||||
- Verify that I have it? [nix REPL][using-nix-repl]
|
||||
|
||||
We used Nix to create our first derivation.
|
||||
|
||||
[wtf-lln]: /lets-learn-nix
|
||||
[wtf-git]: https://git-scm.com/
|
||||
[wtf-derivation]: https://nixos.org/nixos/nix-pills/our-first-derivation.html
|
||||
[wtf-nixpkgs-search]: https://nixos.org/nixos/packages.html?channel=nixos-19.09
|
||||
[using-nix-repl]: /using-the-nix-repl
|
||||
[wtf-home-mgr]: https://github.com/rycee/home-manager
|
||||
[who-ryan]: https://twitter.com/rschmukler
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: "Lets Learn Nix: Tutorial Reproducibility"
|
||||
date: 2020-03-17T18:34:58Z
|
||||
draft: true
|
||||
---
|
||||
|
||||
## Install Nix
|
||||
|
||||
Link to nixos page.
|
||||
|
||||
## The rest
|
||||
|
||||
Start with this...
|
||||
|
||||
```shell
|
||||
$ mkdir ~/lets-learn-nix
|
||||
$ cd ~/lets-learn-nix
|
||||
```
|
||||
|
||||
...done. Copy the following and paste it into a file name `shell.nix`.
|
||||
|
||||
```nix
|
||||
# file: shell.nix
|
||||
let
|
||||
pkgs = import (builtins.fetchGit {
|
||||
url = "https://github.com/NixOS/nixpkgs-channels";
|
||||
ref = "refs/heads/nixos-19.09";
|
||||
}) {}
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
git
|
||||
];
|
||||
NIX_PATH = "nixpkgs=${pkgs}";
|
||||
};
|
||||
```
|
||||
|
||||
...then...
|
||||
|
||||
```shell
|
||||
$ nix-shell
|
||||
```
|
||||
58
users/wpcarro/website/blog/content/english/lets-learn-nix.md
Normal file
58
users/wpcarro/website/blog/content/english/lets-learn-nix.md
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
title: "Lets Learn Nix"
|
||||
date: 2020-03-13T21:50:47Z
|
||||
draft: false
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
[Nix][wtf-nix] may be the most useful tool that I use. I consider it as valuable
|
||||
as [Git][wtf-git] or [Emacs][wtf-emacs]. My friend, David ([@dmjio][who-dmjio]),
|
||||
first introduced me to Nix when we worked together at a Haskell startup in
|
||||
NYC. Before this, I had been managing my system configuration using software
|
||||
that I wrote -- first in Bash, then in Python, then in Golang.
|
||||
|
||||
It took me awhile to understand Nix. I left the NYC startup, joined Google, and
|
||||
relocated to London. Here I met another Nix-enlightened monk, Vincent
|
||||
([@tazjin][who-tazjin]), who patiently taught me enough Nix to become
|
||||
self-reliant and productive.
|
||||
|
||||
Many resources exist to learn Nix; the Nix community on IRC continues to help me
|
||||
and others effectively use Nix. I'm creating this series to write the tutorials
|
||||
that I would have found useful when I started learning Nix. If you are just
|
||||
beginning your Nix journey, I hope these tutorials help you.
|
||||
|
||||
## Goals
|
||||
|
||||
I aim to make each tutorial in the "Let's Learn Nix" series:
|
||||
- Actionable: Readers will be writing code.
|
||||
- Digestible: Readers should be able to finish each tutorial in fifteen minutes.
|
||||
- Reproducible: Readers should expect the output of their code to match what
|
||||
these tutorials claim they should see.
|
||||
|
||||
## About the author
|
||||
|
||||
My name is William ([@wpcarro][who-wpcarro]). My three favorite tools are Git,
|
||||
Emacs, and Nix. I am an American expat currently working at Google in
|
||||
London. While during the day I primarily write Java, Python, and TypeScript, I
|
||||
prefer functional programming. I use Nix to deploy software and manage the
|
||||
multiple machines across which I work.
|
||||
|
||||
## Let's Begin
|
||||
|
||||
Before we get started, Nix is a programming language. To familiarize yourself
|
||||
with the syntax, semantics, and idioms, consider reading this brief [Nix One
|
||||
Pager][nix-1p]. I recommend keeping it around as a reference.
|
||||
|
||||
When I was first learning Nix, I wanted to use it to manage my dotfiles. Our
|
||||
first tutorial will help you get started: [Let's Learn Nix:
|
||||
Dotfiles][lln-dotfiles]
|
||||
|
||||
[wtf-nix]: https://nixos.org
|
||||
[wtf-git]: https://git-scm.com
|
||||
[wtf-emacs]: https://www.gnu.org/software/emacs
|
||||
[who-dmjio]: https://twitter.com/dmjio
|
||||
[who-tazjin]: https://twitter.com/tazjin
|
||||
[who-wpcarro]: https://twitter.com/wpcarro
|
||||
[lln-dotfiles]: /lets-learn-nix-dotfiles
|
||||
[nix-1p]: https://github.com/tazjin/nix-1p
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "Deploy Hugo blog with Nix"
|
||||
date: 2020-03-11T18:42:32Z
|
||||
draft: true
|
||||
---
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: "Self Hosting"
|
||||
date: 2020-03-11T22:53:56Z
|
||||
draft: true
|
||||
---
|
||||
|
||||
12
users/wpcarro/website/blog/default.nix
Normal file
12
users/wpcarro/website/blog/default.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "blog.wpcarro.dev";
|
||||
buildInputs = with pkgs; [ hugo ];
|
||||
src = builtins.path { path = ./.; name = "blog"; };
|
||||
buildPhase = ''
|
||||
mkdir -p $out
|
||||
${pkgs.hugo}/bin/hugo --minify --destination $out
|
||||
'';
|
||||
dontInstall = true;
|
||||
}
|
||||
8
users/wpcarro/website/blog/shell.nix
Normal file
8
users/wpcarro/website/blog/shell.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
let
|
||||
briefcase = import <briefcase> {};
|
||||
pkgs = briefcase.third_party.pkgs;
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
hugo
|
||||
];
|
||||
}
|
||||
5
users/wpcarro/website/blog/themes/tailwind/.gitignore
vendored
Normal file
5
users/wpcarro/website/blog/themes/tailwind/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.vscode/
|
||||
|
||||
node_modules
|
||||
public/build
|
||||
yarn.lock
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
description: ""
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
tags: []
|
||||
---
|
||||
5
users/wpcarro/website/blog/themes/tailwind/i18n/en.yaml
Normal file
5
users/wpcarro/website/blog/themes/tailwind/i18n/en.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
- id: back_home
|
||||
translation: "Back Home"
|
||||
|
||||
- id: not_found_page_title
|
||||
translation: "Whoops! The page you're looking for doesn't exist :("
|
||||
BIN
users/wpcarro/website/blog/themes/tailwind/images/screenshot.png
Normal file
BIN
users/wpcarro/website/blog/themes/tailwind/images/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
users/wpcarro/website/blog/themes/tailwind/images/tn.png
Normal file
BIN
users/wpcarro/website/blog/themes/tailwind/images/tn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
12
users/wpcarro/website/blog/themes/tailwind/layouts/404.html
Normal file
12
users/wpcarro/website/blog/themes/tailwind/layouts/404.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{{ define "heading"}}
|
||||
<div>
|
||||
<a class="text-lg mb-8 inline-block" href="{{ .Site.BaseURL | relLangURL }}">← {{ i18n "back_home" }}</a>
|
||||
<h1 class="text-4xl font-bold">{{ i18n "not_found_page_title" }}</h1>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<section class="mb-24">
|
||||
<img src="{{ "images/404-background.png" | relURL }}" alt="Page Not Found">
|
||||
</section>
|
||||
{{ end }}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<!doctype html>
|
||||
<html lang="{{ .Site.Params.LanguageCode }}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
{{ hugo.Generator }}
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} - {{ .Site.Title }}{{ end }}">
|
||||
<meta name="twitter:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary | plainify }}{{ end }}">
|
||||
<meta name="twitter:site" content="{{ .Site.BaseURL }}">
|
||||
<meta name="twitter:creator" content="{{ .Params.Author }}">
|
||||
<meta name="twitter:image" content="{{ .Site.Params.Avatar | absURL }}">
|
||||
|
||||
<!-- Open-Graph Data -->
|
||||
<meta property="og:locale" content="{{ .Site.Params.LanguageCode }}">
|
||||
<meta property="og:type" content="{{ if .IsHome }}website{{ else }}article{{ end }}">
|
||||
<meta property="og:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} - {{ .Site.Title }}{{ end }}">
|
||||
<meta property="og:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary | plainify }}{{ end }}">
|
||||
<meta property="og:url" content="{{ .Permalink }}">
|
||||
<meta property="og:site_name" content="{{ .Site.Title }}">
|
||||
<meta property="og:image" content="{{ .Site.Params.Avatar | absURL }}">
|
||||
|
||||
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} - {{ .Site.Title }}{{ end }}</title>
|
||||
|
||||
<meta name="author" content="{{ .Site.Params.Author }}">
|
||||
<meta name="description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary | plainify }}{{ end }}">
|
||||
|
||||
<!-- RSS -->
|
||||
{{ with .OutputFormats.Get "RSS" }}
|
||||
<link rel="alternate" href="{{ .RelPermalink | absURL }}" type="application/rss+xml" title="{{ $.Site.Title }}">
|
||||
{{ end }}
|
||||
|
||||
<!-- Translations -->
|
||||
{{ if .IsTranslated }}
|
||||
{{ range .Translations }}
|
||||
<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}" title="{{ .Site.Title }}">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Code+Pro|Arvo:400,700">
|
||||
<link rel="stylesheet" href="{{ "css/theme.css" | absURL }}">
|
||||
<link rel="stylesheet" href="{{ "css/chroma.dracula.css" | absURL }}">
|
||||
</head>
|
||||
<body class="font-serif border-t-4 border-blue-500 antialiased">
|
||||
<div class="w-full p-6 md:w-2/3 md:px-0 md:mx-auto xl:w-2/5">
|
||||
<header class="mb-6">
|
||||
<!-- All the pages must have a heading block, defaults to a link for the home page and a title. -->
|
||||
<div class="mb-6 md:flex md:items-center">
|
||||
{{ block "heading" . }}
|
||||
<div>
|
||||
{{ partial "back-home.html" . }}
|
||||
<h1 class="text-4xl font-bold">{{ .Title }}</h1>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- If the blog has translation, they shoul be displayed here. -->
|
||||
{{ if .IsTranslated }}
|
||||
<nav>
|
||||
{{ range $i, $lang := .Translations }}
|
||||
{{ if $i }}/{{ end }}
|
||||
<a href="{{ .Permalink }}">{{ $lang.Language.LanguageName }}</a>
|
||||
{{ end}}
|
||||
</nav>
|
||||
{{ end }}
|
||||
</header>
|
||||
|
||||
<!-- The content block. -->
|
||||
{{ block "content" . }}{{ end }}
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
© {{ now.Format "2006"}}. Thank you for reading.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{{ template "_internal/google_analytics.html" . }}
|
||||
<script data-ad-client="ca-pub-6018268443649487" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{{ define "content" }}
|
||||
<section class="mb-24">
|
||||
{{ range site.RegularPages.GroupByDate "2006" -}}
|
||||
{{ partial "posts.html" . }}
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{{ define "heading" }}
|
||||
<div>
|
||||
{{ partial "back-home.html" . }}
|
||||
|
||||
<!-- Title and Publication Date -->
|
||||
<h1 class="text-4xl font-bold">{{ .Title }}</h1>
|
||||
<time datetime="{{ .Date.Format "2006-01-02 15:04:05 MST" }}">{{ .Date.Format "02 Jan 2006" }}</time>
|
||||
|
||||
<!-- Tags -->
|
||||
{{ with .Params.tags }}
|
||||
<ol class="mt-4">
|
||||
{{ range . }}
|
||||
<li class="inline-block">
|
||||
<a class="border-none text-gray-800 text-xs bg-gray-400 hover:bg-gray-600 hover:text-white rounded-sm px-3 py-1" href="{{ "tags" | absURL }}/{{ . | urlize }}">{{ . }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ol>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<article class="mb-12">
|
||||
{{ .Content }}
|
||||
|
||||
{{ template "_internal/disqus.html" . }}
|
||||
</article>
|
||||
{{ end }}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{{ define "heading" }}
|
||||
{{ if .Site.Params.Avatar }}
|
||||
<img class="hidden md:block w-20 rounded-full mr-6" src="{{ .Site.Params.Avatar | absURL }}" alt="{{ .Site.Params.Author }}">
|
||||
{{ end }}
|
||||
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold">{{ .Site.Title }}</h1>
|
||||
<p>{{ .Site.Params.tagline }}</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<section class="mb-24">
|
||||
{{ range site.RegularPages.GroupByDate "2006" -}}
|
||||
{{ partial "posts.html" . }}
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<a class="text-lg mb-8 inline-block" href="{{ .Site.BaseURL | relLangURL }}">← {{ i18n "back_home" }}</a>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<div>
|
||||
<h2 class="text-3xl font-bold mb-2">{{ .Key }}</h2>
|
||||
|
||||
<ol>
|
||||
{{ range .Pages -}}
|
||||
<li class="mb-6 md:flex md:flex-row">
|
||||
<time class=" block md:flex-l-24" datetime="{{ .Date.Format "2006-01-02 15:04:05 MST" }}">{{ .Date.Format "Jan 02"}}</time>
|
||||
<a class="text-lg md:ml-12" href="{{ .RelPermalink }}">{{ .Title }}</a>
|
||||
</li>
|
||||
{{- end }}
|
||||
</ol>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{{ define "content" }}
|
||||
<section class="mb-24">
|
||||
<ol class="-mx-2">
|
||||
{{ range .Pages -}}
|
||||
<li class="inline-block mx-2 my-2">
|
||||
<a class="border-none text-gray-800 bg-gray-400 hover:bg-gray-600 hover:text-white rounded-sm px-3 py-1" href="{{ .RelPermalink }}">
|
||||
{{ .Title }}
|
||||
</a>
|
||||
</li>
|
||||
{{- end }}
|
||||
</ol>
|
||||
</section>
|
||||
{{ end }}
|
||||
7
users/wpcarro/website/blog/themes/tailwind/license.md
Normal file
7
users/wpcarro/website/blog/themes/tailwind/license.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Copyright 2019 Ian Rodrigues.
|
||||
|
||||
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.
|
||||
17
users/wpcarro/website/blog/themes/tailwind/package.json
Normal file
17
users/wpcarro/website/blog/themes/tailwind/package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"scripts": {
|
||||
"watch": "cross-env NODE_ENV=development postcss scss/theme.scss -o static/css/theme.css --watch",
|
||||
"build": "cross-env NODE_ENV=production postcss scss/theme.scss -o static/css/theme.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^9.5.1",
|
||||
"tailwindcss": "^1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fullhuman/postcss-purgecss": "^1.2.0",
|
||||
"concurrently": "^4.1.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"postcss-cli": "^6.1.2"
|
||||
}
|
||||
}
|
||||
14
users/wpcarro/website/blog/themes/tailwind/postcss.config.js
Normal file
14
users/wpcarro/website/blog/themes/tailwind/postcss.config.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
const purgecss = require('@fullhuman/postcss-purgecss')({
|
||||
content: ['./layouts/**/*.html'],
|
||||
defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('tailwindcss'),
|
||||
require('autoprefixer'),
|
||||
...process.env.NODE_ENV === 'production'
|
||||
? [purgecss, require('cssnano')]
|
||||
: []
|
||||
]
|
||||
}
|
||||
62
users/wpcarro/website/blog/themes/tailwind/readme.md
Normal file
62
users/wpcarro/website/blog/themes/tailwind/readme.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# TailwindCSS Journal
|
||||
|
||||
_TailwindCSS Journal_ is a minimalist theme for [Hugo](https://gohugo.io) using [TailwindCSS](https://tailwindcss.com).
|
||||
|
||||
Based on [Journal](https://dashdashzako.github.io/hugo-journal-demo/), it also focuses on improving reading experience with no fancy effect.
|
||||
|
||||
It uses [Chroma](https://gohugo.io/content-management/syntax-highlighting/) for the syntaxic coloration of code snippets.
|
||||
|
||||
Demo is available [here](https://ianrodrigues.github.io/hugo-tailwind-journal-demo/).
|
||||
|
||||
## Installation
|
||||
|
||||
Please refer to the [Hugo documentation](https://gohugo.io/themes/installing/).
|
||||
|
||||
## Configuration
|
||||
|
||||
A few parameters should be adjusted in the site config:
|
||||
|
||||
```toml
|
||||
baseURL = "https://username.github.io/"
|
||||
disqusShortname = "username"
|
||||
googleAnalytics = "UA-XXXXXXXXX-X"
|
||||
title = "Tailwind Journal"
|
||||
theme = "hugo-tailwind-journal"
|
||||
pygmentsCodeFences = true
|
||||
pygmentsUseClasses = true
|
||||
|
||||
[taxonomies]
|
||||
tag = "tags"
|
||||
|
||||
[permalinks]
|
||||
posts = "/posts/:year/:month/:title/"
|
||||
|
||||
[params]
|
||||
author = "John Doe"
|
||||
avatar = "images/avatar.jpg"
|
||||
description = "A minimalist journal template for Hugo using TailwindCSS."
|
||||
tagline = "A minimalist journal template for Hugo using TailwindCSS."
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
contentDir = "content/english"
|
||||
languageCode = "en-us"
|
||||
languageName = "🇺🇸 English"
|
||||
weight = 1
|
||||
|
||||
[languages.pt-br]
|
||||
contentDir = "content/portuguese"
|
||||
description = "Um template minimalista para Hugo usando TailwindCSS."
|
||||
languageCode = "pt-br"
|
||||
languageName = "🇧🇷 Português"
|
||||
tagline = "Um template minimalista para Hugo usando TailwindCSS."
|
||||
weight = 2
|
||||
|
||||
[languages.de]
|
||||
contentDir = "content/german"
|
||||
description = "Eine minimalistische Journalvorlage für Hugo mit TailwindCSS."
|
||||
languageCode = "de"
|
||||
languageName = "🇩🇪 Deutsch"
|
||||
tagline = "Eine minimalistische Journalvorlage für Hugo mit TailwindCSS."
|
||||
weight = 3
|
||||
```
|
||||
51
users/wpcarro/website/blog/themes/tailwind/scss/theme.scss
Normal file
51
users/wpcarro/website/blog/themes/tailwind/scss/theme.scss
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
@apply text-gray-800;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, strong {
|
||||
@apply text-gray-900;
|
||||
}
|
||||
|
||||
article > p, ul, ol {
|
||||
@apply text-lg tracking-wide;
|
||||
}
|
||||
|
||||
article > div, p, ul, ol, pre:not(:last-child) {
|
||||
@apply mb-6;
|
||||
}
|
||||
|
||||
article > ol, ul {
|
||||
@apply list-disc ml-8;
|
||||
}
|
||||
|
||||
article > li:not(:last-of-type) {
|
||||
@apply mb-2;
|
||||
}
|
||||
|
||||
article p > code {
|
||||
@apply p-1/2 bg-gray-400;
|
||||
}
|
||||
|
||||
article > h2 {
|
||||
@apply text-2xl my-8 font-bold text-black;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply border-b border-black text-black;
|
||||
}
|
||||
|
||||
/* purgecss ignore */
|
||||
pre.chroma {
|
||||
@apply p-4 overflow-x-auto font-mono text-lg;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
/* purgecss ignore */
|
||||
div.highlight {
|
||||
@apply -mx-12;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
.chroma{color:#f8f8f2;background-color:#282a36}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block}.chroma .hl{display:block;width:100%;background-color:#ffc}.chroma .lnt{margin-right:.4em;padding:0 .4em 0 .4em;color:#7f7f7f}.chroma .ln{margin-right:.4em;padding:0 .4em 0 .4em;color:#7f7f7f}.chroma .k{color:#ff79c6}.chroma .kc{color:#ff79c6}.chroma .kd{color:#8be9fd;font-style:italic}.chroma .kn{color:#ff79c6}.chroma .kp{color:#ff79c6}.chroma .kr{color:#ff79c6}.chroma .kt{color:#8be9fd}.chroma .na{color:#50fa7b}.chroma .nb{color:#8be9fd;font-style:italic}.chroma .nc{color:#50fa7b}.chroma .nf{color:#50fa7b}.chroma .nl{color:#8be9fd;font-style:italic}.chroma .nt{color:#ff79c6}.chroma .nv{color:#8be9fd;font-style:italic}.chroma .vc{color:#8be9fd;font-style:italic}.chroma .vg{color:#8be9fd;font-style:italic}.chroma .vi{color:#8be9fd;font-style:italic}.chroma .s{color:#f1fa8c}.chroma .sa{color:#f1fa8c}.chroma .sb{color:#f1fa8c}.chroma .sc{color:#f1fa8c}.chroma .dl{color:#f1fa8c}.chroma .sd{color:#f1fa8c}.chroma .s2{color:#f1fa8c}.chroma .se{color:#f1fa8c}.chroma .sh{color:#f1fa8c}.chroma .si{color:#f1fa8c}.chroma .sx{color:#f1fa8c}.chroma .sr{color:#f1fa8c}.chroma .s1{color:#f1fa8c}.chroma .ss{color:#f1fa8c}.chroma .m{color:#bd93f9}.chroma .mb{color:#bd93f9}.chroma .mf{color:#bd93f9}.chroma .mh{color:#bd93f9}.chroma .mi{color:#bd93f9}.chroma .il{color:#bd93f9}.chroma .mo{color:#bd93f9}.chroma .o{color:#ff79c6}.chroma .ow{color:#ff79c6}.chroma .c{color:#6272a4}.chroma .ch{color:#6272a4}.chroma .cm{color:#6272a4}.chroma .c1{color:#6272a4}.chroma .cs{color:#6272a4}.chroma .cp{color:#ff79c6}.chroma .cpf{color:#ff79c6}.chroma .gd{color:#8b080b}.chroma .ge{text-decoration:underline}.chroma .gh{font-weight:700}.chroma .gi{font-weight:700}.chroma .go{color:#44475a}.chroma .gu{font-weight:700}.chroma .gl{text-decoration:underline}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}a{background-color:transparent}strong{font-weight:bolder}img{border-style:none}button,input{font-family:inherit;font-size:100%;line-height:1.15;margin:0;overflow:visible}button{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}legend{color:inherit;display:table;max-width:100%;white-space:normal}[type=checkbox],[type=radio],legend{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}[hidden],template{display:none}html{box-sizing:border-box;font-family:sans-serif}*,:after,:before{box-sizing:inherit}h1,h2,p{margin:0}button{background:transparent;padding:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}ol{list-style:none;margin:0;padding:0}html{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{border:0 solid #e2e8f0}img{border-style:solid}input::-webkit-input-placeholder{color:#a0aec0}input::-moz-placeholder{color:#a0aec0}input:-ms-input-placeholder{color:#a0aec0}input::-ms-input-placeholder{color:#a0aec0}input::placeholder{color:#a0aec0}[role=button],button{cursor:pointer}h1,h2{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input{padding:0;line-height:inherit;color:inherit}canvas,img{display:block;vertical-align:middle}img{max-width:100%;height:auto}.bg-gray-200{background-color:#edf2f7}.bg-gray-400{background-color:#cbd5e0}.hover\:bg-gray-600:hover{background-color:#718096}.border-blue-500{border-color:#4299e1}.rounded-sm{border-radius:.125rem}.rounded-full{border-radius:9999px}.border-none{border-style:none}.border-t-4{border-top-width:4px}.block{display:block}.inline-block{display:inline-block}.hidden{display:none}.font-serif{font-family:Arvo}.font-bold{font-weight:700}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.mb-2{margin-bottom:.5rem}.mt-4{margin-top:1rem}.mr-6{margin-right:1.5rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mb-12{margin-bottom:3rem}.mb-24{margin-bottom:6rem}.p-6{padding:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-3{padding-left:.75rem;padding-right:.75rem}.text-gray-800{color:#2d3748}.hover\:text-white:hover{color:#fff}.text-xs{font-size:.75rem}.text-lg{font-size:1.125rem}.text-3xl{font-size:1.875rem}.text-4xl{font-size:2.25rem}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-20{width:5rem}.w-full{width:100%}body{color:#2d3748}h1,h2,strong{color:#1a202c}article>p,ol{font-size:1.125rem;letter-spacing:.025em}article>div,ol,p{margin-bottom:1.5rem}article>ol{list-style-type:disc;margin-left:2rem}article>li:not(:last-of-type){margin-bottom:.5rem}article>h2{font-size:1.5rem;margin-top:2rem;margin-bottom:2rem;font-weight:700;color:#000}a{border-bottom-width:1px;border-color:#000;color:#000}pre.chroma{padding:1rem;overflow-x:auto;font-family:Source Code Pro;font-size:1.125rem}@media (min-width:768px){div.highlight{margin-left:-3rem;margin-right:-3rem}}@media (min-width:768px){.md\:block{display:block}.md\:flex{display:-webkit-box;display:flex}.md\:flex-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row}.md\:items-center{-webkit-box-align:center;align-items:center}.md\:flex-l-24{-webkit-box-flex:0;flex:0 0 6rem}.md\:mx-auto{margin-left:auto;margin-right:auto}.md\:ml-12{margin-left:3rem}.md\:px-0{padding-left:0;padding-right:0}.md\:w-2\/3{width:66.666667%}}@media (min-width:1280px){.xl\:w-2\/5{width:40%}}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
flex: {
|
||||
'l-24': '0 0 6rem'
|
||||
},
|
||||
fontFamily: {
|
||||
serif: ['Arvo'],
|
||||
mono: ['Source Code Pro']
|
||||
},
|
||||
padding: {
|
||||
'1/2': '0.125rem'
|
||||
}
|
||||
}
|
||||
},
|
||||
variants: {},
|
||||
plugins: [],
|
||||
}
|
||||
12
users/wpcarro/website/blog/themes/tailwind/theme.toml
Normal file
12
users/wpcarro/website/blog/themes/tailwind/theme.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
name = "Tailwind Journal"
|
||||
license = "MIT"
|
||||
licenselink = "https://github.com/ianrodrigues/hugo-tailwind-journal/blob/master/license.md"
|
||||
description = "A minimalist journal template for Hugo using TailwindCSS."
|
||||
homepage = "https://github.com/ianrodrigues/hugo-tailwind-journal"
|
||||
tags = ["minimalist", "reading", "blog", "tailwindcss"]
|
||||
features = ["blog"]
|
||||
min_version = "0.54.0"
|
||||
|
||||
[author]
|
||||
name = "Ian Rodrigues"
|
||||
homepage = "https://ianrodrigues.dev"
|
||||
13
users/wpcarro/website/default.nix
Normal file
13
users/wpcarro/website/default.nix
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{ pkgs, briefcase, ... }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "wpcarro.dev";
|
||||
src = builtins.path { path = ./.; name = "website"; };
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp $src/index.html $out
|
||||
|
||||
mkdir -p $out/habits
|
||||
cp -r ${briefcase.website.habit-screens} $out/habits/index.html
|
||||
'';
|
||||
}
|
||||
2
users/wpcarro/website/goals/.envrc
Normal file
2
users/wpcarro/website/goals/.envrc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
source_up
|
||||
use_nix
|
||||
3
users/wpcarro/website/goals/.gitignore
vendored
Normal file
3
users/wpcarro/website/goals/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/.cache
|
||||
/dist/**/*
|
||||
/node_modules
|
||||
5
users/wpcarro/website/goals/README.md
Normal file
5
users/wpcarro/website/goals/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Goals
|
||||
|
||||
Kent C. Dodds taught me that I can create a React website without any bundling
|
||||
software. To practice this, I created a simple React app to track some of my
|
||||
goals. Notice how I wrote JSX inside of a `<script type="text/babel">` tag.
|
||||
19
users/wpcarro/website/goals/default.nix.ignore
Normal file
19
users/wpcarro/website/goals/default.nix.ignore
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "goals-webpage";
|
||||
src = builtins.path { path = ./.; name = "goals"; };
|
||||
buildInputs = with pkgs; [
|
||||
nodejs
|
||||
# Exposes lscpu for parcel.js
|
||||
utillinux
|
||||
];
|
||||
# parcel.js needs number of CPUs
|
||||
PARCEL_WORKERS = "1";
|
||||
buildPhase = ''
|
||||
npx parcel build src/index.html --public-url ./
|
||||
'';
|
||||
installPhase = ''
|
||||
mv dist $out
|
||||
'';
|
||||
}
|
||||
28
users/wpcarro/website/goals/package.json
Normal file
28
users/wpcarro/website/goals/package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "tailwindcss",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "parcel src/index.html & npx tsc --watch --noEmit",
|
||||
"prettier": "prettier --ignore-path .gitignore --write \"**/*.{js,ts,jsx,tsx,html,css.json}\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^13.9.3",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prettier": "^2.0.2",
|
||||
"tailwindcss": "^1.2.0",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.2.5",
|
||||
"@types/react": "^16.9.25",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/react-redux": "^7.1.7",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-router-dom": "^5.1.2"
|
||||
}
|
||||
}
|
||||
5
users/wpcarro/website/goals/postcss.config.js
Normal file
5
users/wpcarro/website/goals/postcss.config.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
const tailwindcss = require("tailwindcss");
|
||||
|
||||
module.exports = {
|
||||
plugins: [tailwindcss("./tailwind.config.js")],
|
||||
};
|
||||
9
users/wpcarro/website/goals/shell.nix
Normal file
9
users/wpcarro/website/goals/shell.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
let
|
||||
briefcase = import <briefcase> {};
|
||||
pkgs = briefcase.third_party.pkgs;
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
nodejs
|
||||
yarn
|
||||
];
|
||||
}
|
||||
132
users/wpcarro/website/goals/src/App.tsx
Normal file
132
users/wpcarro/website/goals/src/App.tsx
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import React from "react";
|
||||
|
||||
function ProgressBar(props: {
|
||||
done: number;
|
||||
total: number;
|
||||
units: string;
|
||||
color: string;
|
||||
}) {
|
||||
const { done, total, units, color } = props;
|
||||
const width = Math.floor((done / total) * 100);
|
||||
const rest = 100 - width;
|
||||
|
||||
let [fg, bg] = [`bg-${color}-600`, `bg-${color}-100`];
|
||||
|
||||
if (color === "white") {
|
||||
[fg, bg] = ["bg-gray-600", "bg-gray-100"];
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`relative ${bg} h-5`}>
|
||||
<div
|
||||
className={`${fg} h-5 absolute top-0 left-0`}
|
||||
style={{ width: `${width}%` }}
|
||||
></div>
|
||||
<p className="absolute text-xs pl-1 pt-1">
|
||||
{done} of {total} {units}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Goal(props: {
|
||||
subject: string;
|
||||
goal: string;
|
||||
done: number;
|
||||
total: number;
|
||||
units: string;
|
||||
color: string;
|
||||
}) {
|
||||
const { subject, goal, done, total, units, color } = props;
|
||||
const width = "6em";
|
||||
|
||||
const Tr = (props: {
|
||||
label: string;
|
||||
value: string;
|
||||
valueComponent?: React.ReactElement;
|
||||
}) => (
|
||||
<tr className="flex py-2">
|
||||
<td className="text-gray-600" style={{ width: width }}>
|
||||
{props.label}
|
||||
</td>
|
||||
<td className="flex-1">
|
||||
{props.valueComponent ? props.valueComponent : props.value}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
return (
|
||||
<table className="w-full mb-10">
|
||||
<tbody>
|
||||
<Tr label="Subject" value={subject} />
|
||||
<Tr label="Goal" value={goal} />
|
||||
<Tr
|
||||
label="Progress"
|
||||
value={goal}
|
||||
valueComponent={
|
||||
<ProgressBar
|
||||
done={done}
|
||||
total={total}
|
||||
units={units}
|
||||
color={color}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
function Copy(props: { children: React.ReactNode }) {
|
||||
return <p className="pb-4 leading-loose">{props.children}</p>;
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="container mx-auto font-mono">
|
||||
<section>
|
||||
<h1 className="text-center pt-12 pb-6">Goals</h1>
|
||||
<Copy>
|
||||
For me, a goal is something that is difficult for me to complete but
|
||||
easy for me to measure. I tend to add new goals as time progresses,
|
||||
mistakenly assuming that I can support additional goals for free. To
|
||||
counterbalance my tendancy to casually accumulate goals, I aim to only
|
||||
have three goals; I will not add a new goal until I complete an
|
||||
existing goal. I created and published this page to clarify that idea.
|
||||
</Copy>
|
||||
<Copy>
|
||||
Here are my current goals and the progress I have made towards
|
||||
achieving them.
|
||||
</Copy>
|
||||
</section>
|
||||
<section className="pt-4">
|
||||
<Goal
|
||||
subject="Meditation"
|
||||
goal="Meditate for 10,000 hours"
|
||||
done={100}
|
||||
total={10000}
|
||||
units="hrs"
|
||||
color="purple"
|
||||
/>
|
||||
<Goal
|
||||
subject="Debt"
|
||||
goal="Pay my student debt balance"
|
||||
done={30000}
|
||||
total={70000}
|
||||
units="USD"
|
||||
color="green"
|
||||
/>
|
||||
<Goal
|
||||
subject="Brazilian Jiu Jitsu"
|
||||
goal="Train until an instructor gives me a black belt"
|
||||
done={1}
|
||||
total={5}
|
||||
units="belts"
|
||||
color="white"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
3
users/wpcarro/website/goals/src/index.css
Normal file
3
users/wpcarro/website/goals/src/index.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
11
users/wpcarro/website/goals/src/index.html
Normal file
11
users/wpcarro/website/goals/src/index.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" href="./index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="mount"></div>
|
||||
<script src="./index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
5
users/wpcarro/website/goals/src/index.tsx
Normal file
5
users/wpcarro/website/goals/src/index.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("mount"));
|
||||
7
users/wpcarro/website/goals/tailwind.config.js
Normal file
7
users/wpcarro/website/goals/tailwind.config.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [],
|
||||
};
|
||||
19
users/wpcarro/website/goals/tsconfig.json
Normal file
19
users/wpcarro/website/goals/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
5670
users/wpcarro/website/goals/yarn.lock
Normal file
5670
users/wpcarro/website/goals/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
2
users/wpcarro/website/habit-screens/.envrc
Normal file
2
users/wpcarro/website/habit-screens/.envrc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
source_up
|
||||
use_nix
|
||||
2
users/wpcarro/website/habit-screens/.gitignore
vendored
Normal file
2
users/wpcarro/website/habit-screens/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/elm-stuff
|
||||
/Main.min.js
|
||||
31
users/wpcarro/website/habit-screens/README.md
Normal file
31
users/wpcarro/website/habit-screens/README.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Habit Screens
|
||||
|
||||
Problem: I would like to increase the rate at which I complete my daily, weekly,
|
||||
monthly, yearly habits.
|
||||
|
||||
Solution: Habit Screens are mounted in strategic locations throughout my
|
||||
apartment. Each Habit Screen displays the habits that I should complete that
|
||||
day, and I can tap each item to mark it as complete. I will encounter the Habit
|
||||
Screens in my bedroom, kitchen, and bathroom, so I will have adequate "cues" to
|
||||
focus my attention. By marking each item as complete and tracking the results
|
||||
over time, I will have more incentive to maintain my consistency
|
||||
(i.e. "reward").
|
||||
|
||||
## Elm
|
||||
|
||||
Elm has one of the best developer experiences that I'm aware of. The error
|
||||
messages are helpful and the entire experience is optimized to improve the ease
|
||||
of writing web applications.
|
||||
|
||||
### Developing
|
||||
|
||||
If you're interested in contributing, the following will create an environment
|
||||
in which you can develop:
|
||||
|
||||
```shell
|
||||
$ nix-shell
|
||||
$ npx tailwindcss build index.css -o output.css
|
||||
$ elm-live -- src/Main.elm --output=Main.min.js
|
||||
```
|
||||
|
||||
You can now view your web client at `http://localhost:8000`!
|
||||
61
users/wpcarro/website/habit-screens/default.nix
Normal file
61
users/wpcarro/website/habit-screens/default.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{ pkgs ? <nixpkgs> , ... }:
|
||||
|
||||
with pkgs;
|
||||
|
||||
let
|
||||
mkDerivation =
|
||||
{ srcs ? ./elm-srcs.nix
|
||||
, src
|
||||
, name
|
||||
, srcdir ? "./src"
|
||||
, targets ? []
|
||||
, registryDat ? ./registry.dat
|
||||
, outputJavaScript ? false
|
||||
}:
|
||||
stdenv.mkDerivation {
|
||||
inherit name src;
|
||||
|
||||
buildInputs = [ elmPackages.elm ]
|
||||
++ lib.optional outputJavaScript nodePackages_10_x.uglify-js;
|
||||
|
||||
buildPhase = pkgs.elmPackages.fetchElmDeps {
|
||||
elmPackages = import srcs;
|
||||
elmVersion = "0.19.1";
|
||||
inherit registryDat;
|
||||
};
|
||||
|
||||
installPhase = let
|
||||
elmfile = module: "${srcdir}/${builtins.replaceStrings ["."] ["/"] module}.elm";
|
||||
extension = if outputJavaScript then "js" else "html";
|
||||
in ''
|
||||
mkdir -p $out/share/doc
|
||||
${lib.concatStrings (map (module: ''
|
||||
echo "compiling ${elmfile module}"
|
||||
elm make ${elmfile module} --output $out/${module}.${extension} --docs $out/share/doc/${module}.json
|
||||
${lib.optionalString outputJavaScript ''
|
||||
echo "minifying ${elmfile module}"
|
||||
uglifyjs $out/${module}.${extension} --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \
|
||||
| uglifyjs --mangle --output=$out/${module}.min.${extension}
|
||||
''}
|
||||
'') targets)}
|
||||
'';
|
||||
};
|
||||
mainDotElm = mkDerivation {
|
||||
name = "elm-app-0.1.0";
|
||||
srcs = ./elm-srcs.nix;
|
||||
src = ./.;
|
||||
targets = ["Main"];
|
||||
srcdir = "./src";
|
||||
outputJavaScript = true;
|
||||
};
|
||||
in stdenv.mkDerivation {
|
||||
name = "habit-screens";
|
||||
buildInputs = [];
|
||||
src = builtins.path { path = ./.; name = "habit-screens"; };
|
||||
buildPhase = ''
|
||||
mkdir -p $out
|
||||
cp index.html output.css ${mainDotElm}/Main.min.js $out
|
||||
'';
|
||||
dontInstall = true;
|
||||
}
|
||||
|
||||
43
users/wpcarro/website/habit-screens/design.md
Normal file
43
users/wpcarro/website/habit-screens/design.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Habit Screens
|
||||
|
||||
## MVP
|
||||
|
||||
One Android tablet mounted on my bedroom wall displaying habits for that day. I
|
||||
can toggle the done/todo states on each item by tapping it. There is no
|
||||
server. All of the habits are defined in the client-side codebase. The
|
||||
application is available online at wpcarro.dev.
|
||||
|
||||
## Ideal
|
||||
|
||||
Three Android tablets: one mounted in my bedroom, another in my bathroom, and a
|
||||
third in my kitchen. Each tablet has a view of the current state of the
|
||||
application and updates in soft real-time.
|
||||
|
||||
I track the rates at which I complete each habit and compile all of the metrics
|
||||
into a dashboard. When I move a habit from Saturday to Sunday or from Wednesday
|
||||
to Monday, it doesn't break the tracking.
|
||||
|
||||
When I complete a habit, it quickly renders some consistency information like
|
||||
"completing rate since Monday" and "length of current streak".
|
||||
|
||||
I don't consider this application that sensitive, but for security purposes I
|
||||
would like this application to be accessible within a private network. This is
|
||||
something I don't know too much about setting up, but I don't want anyone to be
|
||||
able to visit www.BillAndHisHabits.com and change the states of my habits and
|
||||
affect the tracking data. Nor do I want anyone to be able to make HTTP requests
|
||||
to my server to alter the state of the application without my permission.
|
||||
|
||||
## Client
|
||||
|
||||
Language: Elm
|
||||
|
||||
### Updates across devices
|
||||
|
||||
Instead of setting up sockets on my server and subscribing to them from the
|
||||
client, I think each device should poll the server once every second (or fewer)
|
||||
to maintain UI consistency.
|
||||
|
||||
## Server
|
||||
|
||||
Language: Haskell
|
||||
Database: SQLite
|
||||
77
users/wpcarro/website/habit-screens/elm-srcs.nix
Normal file
77
users/wpcarro/website/habit-screens/elm-srcs.nix
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
|
||||
"elm-community/maybe-extra" = {
|
||||
sha256 = "0qslmgswa625d218djd3p62pnqcrz38f5p558mbjl6kc1ss0kzv3";
|
||||
version = "5.2.0";
|
||||
};
|
||||
|
||||
"elm/html" = {
|
||||
sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k";
|
||||
version = "1.0.0";
|
||||
};
|
||||
|
||||
"elm-community/random-extra" = {
|
||||
sha256 = "1dg2nz77w2cvp16xazbdsxkkw0xc9ycqpkd032faqdyky6gmz9g6";
|
||||
version = "3.1.0";
|
||||
};
|
||||
|
||||
"elm/svg" = {
|
||||
sha256 = "1cwcj73p61q45wqwgqvrvz3aypjyy3fw732xyxdyj6s256hwkn0k";
|
||||
version = "1.0.1";
|
||||
};
|
||||
|
||||
"justinmimbs/date" = {
|
||||
sha256 = "1f0wcl8yhlvp3x4rj53rdy4r4ga7lkl6n8fdfh6b96scz2rnxmd4";
|
||||
version = "3.2.1";
|
||||
};
|
||||
|
||||
"elm/browser" = {
|
||||
sha256 = "0nagb9ajacxbbg985r4k9h0jadqpp0gp84nm94kcgbr5sf8i9x13";
|
||||
version = "1.0.2";
|
||||
};
|
||||
|
||||
"elm/core" = {
|
||||
sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf";
|
||||
version = "1.0.5";
|
||||
};
|
||||
|
||||
"elm-community/list-extra" = {
|
||||
sha256 = "1ayv3148drynqnxdfwpjxal8vwzgsjqanjg7yxp6lhdcbkxgd3vd";
|
||||
version = "8.2.3";
|
||||
};
|
||||
|
||||
"elm/random" = {
|
||||
sha256 = "138n2455wdjwa657w6sjq18wx2r0k60ibpc4frhbqr50sncxrfdl";
|
||||
version = "1.0.0";
|
||||
};
|
||||
|
||||
"elm/time" = {
|
||||
sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1";
|
||||
version = "1.0.0";
|
||||
};
|
||||
|
||||
"elm/json" = {
|
||||
sha256 = "0kjwrz195z84kwywaxhhlnpl3p251qlbm5iz6byd6jky2crmyqyh";
|
||||
version = "1.1.3";
|
||||
};
|
||||
|
||||
"elm/parser" = {
|
||||
sha256 = "0a3cxrvbm7mwg9ykynhp7vjid58zsw03r63qxipxp3z09qks7512";
|
||||
version = "1.1.0";
|
||||
};
|
||||
|
||||
"owanturist/elm-union-find" = {
|
||||
sha256 = "13gm7msnp0gr1lqia5m7m4lhy3m6kvjg37d304whb3psn88wqhj5";
|
||||
version = "1.0.0";
|
||||
};
|
||||
|
||||
"elm/url" = {
|
||||
sha256 = "0av8x5syid40sgpl5vd7pry2rq0q4pga28b4yykn9gd9v12rs3l4";
|
||||
version = "1.0.0";
|
||||
};
|
||||
|
||||
"elm/virtual-dom" = {
|
||||
sha256 = "0q1v5gi4g336bzz1lgwpn5b1639lrn63d8y6k6pimcyismp2i1yg";
|
||||
version = "1.0.2";
|
||||
};
|
||||
}
|
||||
32
users/wpcarro/website/habit-screens/elm.json
Normal file
32
users/wpcarro/website/habit-screens/elm.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/svg": "1.0.1",
|
||||
"elm/time": "1.0.0",
|
||||
"elm-community/list-extra": "8.2.3",
|
||||
"elm-community/maybe-extra": "5.2.0",
|
||||
"elm-community/random-extra": "3.1.0",
|
||||
"justinmimbs/date": "3.2.1"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/parser": "1.1.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2",
|
||||
"owanturist/elm-union-find": "1.0.0"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
||||
3
users/wpcarro/website/habit-screens/index.css
Normal file
3
users/wpcarro/website/habit-screens/index.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
21
users/wpcarro/website/habit-screens/index.html
Normal file
21
users/wpcarro/website/habit-screens/index.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Elm SPA</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Chilanka">
|
||||
<link rel="stylesheet" href="./output.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Chilanka';
|
||||
}
|
||||
</style>
|
||||
<script src="./Main.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mount"></div>
|
||||
<script>
|
||||
Elm.Main.init({node: document.getElementById("mount")});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
103571
users/wpcarro/website/habit-screens/output.css
Normal file
103571
users/wpcarro/website/habit-screens/output.css
Normal file
File diff suppressed because it is too large
Load diff
BIN
users/wpcarro/website/habit-screens/registry.dat
Normal file
BIN
users/wpcarro/website/habit-screens/registry.dat
Normal file
Binary file not shown.
10
users/wpcarro/website/habit-screens/shell.nix
Normal file
10
users/wpcarro/website/habit-screens/shell.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
let
|
||||
briefcase = import <briefcase> {};
|
||||
pkgs = briefcase.third_party.pkgs;
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs.elmPackages; [
|
||||
elm
|
||||
elm-format
|
||||
elm-live
|
||||
];
|
||||
}
|
||||
463
users/wpcarro/website/habit-screens/src/Habits.elm
Normal file
463
users/wpcarro/website/habit-screens/src/Habits.elm
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
module Habits exposing (render)
|
||||
|
||||
import Browser
|
||||
import Date exposing (Date)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (..)
|
||||
import Set exposing (Set)
|
||||
import State exposing (HabitType(..))
|
||||
import Time exposing (Weekday(..))
|
||||
import UI
|
||||
import Utils exposing (Strategy(..))
|
||||
|
||||
|
||||
morning : List State.Habit
|
||||
morning =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.Morning
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 1, "Make bed" )
|
||||
, ( 2, "Brush teeth" )
|
||||
, ( 30, "Run (15 minutes)" )
|
||||
, ( 10, "Shower" )
|
||||
, ( 10, "Meditate" )
|
||||
]
|
||||
|
||||
|
||||
evening : List State.Habit
|
||||
evening =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.Evening
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 30, "Read" )
|
||||
, ( 1, "Record in habit Journal" )
|
||||
]
|
||||
|
||||
|
||||
monday : List ( Int, String )
|
||||
monday =
|
||||
[ ( 90, "Bikram Yoga @ 17:00" )
|
||||
]
|
||||
|
||||
|
||||
tuesday : List ( Int, String )
|
||||
tuesday =
|
||||
[ ( 90, "Bikram Yoga @ 18:00" )
|
||||
]
|
||||
|
||||
|
||||
wednesday : List ( Int, String )
|
||||
wednesday =
|
||||
[ ( 5, "Shave" )
|
||||
, ( 90, "Bikram Yoga @ 17:00" )
|
||||
]
|
||||
|
||||
|
||||
thursday : List ( Int, String )
|
||||
thursday =
|
||||
[]
|
||||
|
||||
|
||||
friday : List ( Int, String )
|
||||
friday =
|
||||
[ ( 60, "Bikram Yoga @ 17:00" )
|
||||
, ( 3, "Take-out trash" )
|
||||
, ( 60, "Shop for groceries" )
|
||||
]
|
||||
|
||||
|
||||
saturday : List ( Int, String )
|
||||
saturday =
|
||||
[ ( 60, "Warm Yin Yoga @ 15:00" )
|
||||
]
|
||||
|
||||
|
||||
sunday : List ( Int, String )
|
||||
sunday =
|
||||
[ ( 1, "Shampoo" )
|
||||
, ( 5, "Shave" )
|
||||
, ( 1, "Trim nails" )
|
||||
, ( 1, "Combine trash cans" )
|
||||
, ( 10, "Mop tile and wood floors" )
|
||||
, ( 10, "Laundry" )
|
||||
, ( 5, "Vacuum bedroom" )
|
||||
, ( 5, "Clean desk" )
|
||||
]
|
||||
|
||||
|
||||
payday : List State.Habit
|
||||
payday =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.Payday
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 1, "Ensure \"Emergency\" fund has a balance of 1000 GBP" )
|
||||
, ( 1, "Open \"finances_2020\" Google Sheet" )
|
||||
, ( 1, "Settle up with Mimi on TransferWise" )
|
||||
, ( 1, "Adjust GBP:USD exchange rate" )
|
||||
, ( 1, "Adjust \"Stocks (after tax)\" to reflect amount Google sent" )
|
||||
, ( 1, "Add remaining cash to \"Carryover (cash)\"" )
|
||||
, ( 1, "Adjust \"Paycheck\" to reflect amount Google sent" )
|
||||
, ( 5, "In the \"International Xfer\" table, send \"Xfer amount\" from Monzo to USAA" )
|
||||
, ( 10, "Go to an ATM and extract the amount in \"ATM withdrawal\"" )
|
||||
, ( 0, "Await the TransferWise transaction to complete and pay MyFedLoan in USD" )
|
||||
]
|
||||
|
||||
|
||||
firstOfTheMonth : List State.Habit
|
||||
firstOfTheMonth =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.FirstOfTheMonth
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 10, "Create habit template in journal" )
|
||||
, ( 30, "Assess previous month's performance" )
|
||||
, ( 5, "Register for Bikram Yoga classes" )
|
||||
]
|
||||
|
||||
|
||||
firstOfTheYear : List State.Habit
|
||||
firstOfTheYear =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.FirstOfTheYear
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 60, "Write a post mortem for the previous year" )
|
||||
]
|
||||
|
||||
|
||||
habitTypes :
|
||||
{ includeMorning : Bool
|
||||
, includeEvening : Bool
|
||||
, date : Date
|
||||
}
|
||||
-> List State.HabitType
|
||||
habitTypes { includeMorning, includeEvening, date } =
|
||||
let
|
||||
habitTypePredicates : List ( State.HabitType, Date -> Bool )
|
||||
habitTypePredicates =
|
||||
[ ( Morning, \_ -> includeMorning )
|
||||
, ( DayOfWeek, \_ -> True )
|
||||
, ( Payday, \x -> Date.day x == 25 )
|
||||
, ( FirstOfTheMonth, \x -> Date.day x == 1 )
|
||||
, ( FirstOfTheYear, \x -> Date.day x == 1 && Date.monthNumber x == 1 )
|
||||
, ( Evening, \_ -> includeEvening )
|
||||
]
|
||||
in
|
||||
habitTypePredicates
|
||||
|> List.filter (\( _, predicate ) -> predicate date)
|
||||
|> List.map (\( habitType, _ ) -> habitType)
|
||||
|
||||
|
||||
habitsFor : State.HabitType -> Weekday -> List State.Habit
|
||||
habitsFor habitType weekday =
|
||||
case habitType of
|
||||
Morning ->
|
||||
morning
|
||||
|
||||
Evening ->
|
||||
evening
|
||||
|
||||
DayOfWeek ->
|
||||
let
|
||||
toHabit : List ( Int, String ) -> List State.Habit
|
||||
toHabit =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.DayOfWeek
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
in
|
||||
case weekday of
|
||||
Mon ->
|
||||
toHabit monday
|
||||
|
||||
Tue ->
|
||||
toHabit tuesday
|
||||
|
||||
Wed ->
|
||||
toHabit wednesday
|
||||
|
||||
Thu ->
|
||||
toHabit thursday
|
||||
|
||||
Fri ->
|
||||
toHabit friday
|
||||
|
||||
Sat ->
|
||||
toHabit saturday
|
||||
|
||||
Sun ->
|
||||
toHabit sunday
|
||||
|
||||
Payday ->
|
||||
payday
|
||||
|
||||
FirstOfTheMonth ->
|
||||
firstOfTheMonth
|
||||
|
||||
FirstOfTheYear ->
|
||||
firstOfTheYear
|
||||
|
||||
|
||||
weekdayLabelFor : Weekday -> State.WeekdayLabel
|
||||
weekdayLabelFor weekday =
|
||||
case weekday of
|
||||
Mon ->
|
||||
"Monday"
|
||||
|
||||
Tue ->
|
||||
"Tuesday"
|
||||
|
||||
Wed ->
|
||||
"Wednesday"
|
||||
|
||||
Thu ->
|
||||
"Thursday"
|
||||
|
||||
Fri ->
|
||||
"Friday"
|
||||
|
||||
Sat ->
|
||||
"Saturday"
|
||||
|
||||
Sun ->
|
||||
"Sunday"
|
||||
|
||||
|
||||
timeRemaining : State.WeekdayLabel -> State.CompletedHabits -> List State.Habit -> Int
|
||||
timeRemaining weekdayLabel completed habits =
|
||||
habits
|
||||
|> List.indexedMap
|
||||
(\i { label, minutesDuration } ->
|
||||
if Set.member ( weekdayLabel, label ) completed then
|
||||
0
|
||||
|
||||
else
|
||||
minutesDuration
|
||||
)
|
||||
|> List.sum
|
||||
|
||||
|
||||
render : State.Model -> Html State.Msg
|
||||
render { today, visibleDayOfWeek, completed, includeMorning, includeEvening } =
|
||||
case ( today, visibleDayOfWeek ) of
|
||||
( Just todaysDate, Just visibleWeekday ) ->
|
||||
let
|
||||
todaysWeekday : Weekday
|
||||
todaysWeekday =
|
||||
Date.weekday todaysDate
|
||||
|
||||
habits : List State.Habit
|
||||
habits =
|
||||
habitTypes
|
||||
{ includeMorning = includeMorning
|
||||
, includeEvening = includeEvening
|
||||
, date = todaysDate
|
||||
}
|
||||
|> List.map (\habitType -> habitsFor habitType todaysWeekday)
|
||||
|> List.concat
|
||||
in
|
||||
div
|
||||
[ Utils.class
|
||||
[ Always "max-w-xl mx-auto py-6 px-6"
|
||||
, When (todaysWeekday /= visibleWeekday) "pt-20"
|
||||
]
|
||||
]
|
||||
[ header []
|
||||
[ if todaysWeekday /= visibleWeekday then
|
||||
div [ class "text-center w-full bg-blue-600 text-white fixed top-0 left-0 px-3 py-4" ]
|
||||
[ p [ class "py-2 inline pr-5" ]
|
||||
[ text "As you are not viewing today's habits, the UI is in read-only mode" ]
|
||||
, UI.button
|
||||
[ class "bg-blue-200 px-4 py-2 rounded text-blue-600 text-xs font-bold"
|
||||
, onClick State.ViewToday
|
||||
]
|
||||
[ text "View Today's Habits" ]
|
||||
]
|
||||
|
||||
else
|
||||
text ""
|
||||
, div [ class "flex center" ]
|
||||
[ UI.button
|
||||
[ class "w-1/4 text-gray-500"
|
||||
, onClick State.ViewPrevious
|
||||
]
|
||||
[ text "‹ previous" ]
|
||||
, h1 [ class "font-bold text-blue-500 text-3xl text-center w-full" ]
|
||||
[ text (weekdayLabelFor visibleWeekday) ]
|
||||
, UI.button
|
||||
[ class "w-1/4 text-gray-500"
|
||||
, onClick State.ViewNext
|
||||
]
|
||||
[ text "next ›" ]
|
||||
]
|
||||
]
|
||||
, if todaysWeekday == visibleWeekday then
|
||||
p [ class "text-center pt-1 pb-4" ]
|
||||
[ let
|
||||
t : Int
|
||||
t =
|
||||
timeRemaining (weekdayLabelFor todaysWeekday) completed habits
|
||||
in
|
||||
if t == 0 then
|
||||
text "Nothing to do!"
|
||||
|
||||
else
|
||||
text
|
||||
((habits
|
||||
|> timeRemaining (weekdayLabelFor todaysWeekday) completed
|
||||
|> String.fromInt
|
||||
)
|
||||
++ " minutes remaining"
|
||||
)
|
||||
]
|
||||
|
||||
else
|
||||
text ""
|
||||
, if todaysWeekday == visibleWeekday then
|
||||
div []
|
||||
[ UI.button
|
||||
[ onClick
|
||||
(if Set.size completed == 0 then
|
||||
State.DoNothing
|
||||
|
||||
else
|
||||
State.ClearAll
|
||||
)
|
||||
, Utils.class
|
||||
[ Always "ml-10 px-3"
|
||||
, If (Set.size completed == 0)
|
||||
"text-gray-500 cursor-not-allowed"
|
||||
"text-red-500 underline cursor-pointer"
|
||||
]
|
||||
]
|
||||
[ let
|
||||
numCompleted : Int
|
||||
numCompleted =
|
||||
habits
|
||||
|> List.indexedMap (\i { label } -> ( i, label ))
|
||||
|> List.filter
|
||||
(\( i, label ) ->
|
||||
Set.member
|
||||
( weekdayLabelFor todaysWeekday, label )
|
||||
completed
|
||||
)
|
||||
|> List.length
|
||||
in
|
||||
if numCompleted == 0 then
|
||||
text "Clear"
|
||||
|
||||
else
|
||||
text ("Clear (" ++ String.fromInt numCompleted ++ ")")
|
||||
]
|
||||
, UI.button
|
||||
[ onClick State.ToggleMorning
|
||||
, Utils.class
|
||||
[ Always "px-3 underline"
|
||||
, If includeMorning
|
||||
"text-gray-600"
|
||||
"text-blue-600"
|
||||
]
|
||||
]
|
||||
[ text
|
||||
(if includeMorning then
|
||||
"Hide Morning"
|
||||
|
||||
else
|
||||
"Show Morning"
|
||||
)
|
||||
]
|
||||
, UI.button
|
||||
[ Utils.class
|
||||
[ Always "px-3 underline"
|
||||
, If includeEvening
|
||||
"text-gray-600"
|
||||
"text-blue-600"
|
||||
]
|
||||
, onClick State.ToggleEvening
|
||||
]
|
||||
[ text
|
||||
(if includeEvening then
|
||||
"Hide Evening"
|
||||
|
||||
else
|
||||
"Show Evening"
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
else
|
||||
text ""
|
||||
, ul [ class "pb-10" ]
|
||||
(habits
|
||||
|> List.indexedMap
|
||||
(\i { label, minutesDuration } ->
|
||||
let
|
||||
isCompleted : Bool
|
||||
isCompleted =
|
||||
Set.member ( weekdayLabelFor todaysWeekday, label ) completed
|
||||
in
|
||||
li [ class "text-xl list-disc ml-6" ]
|
||||
[ if todaysWeekday == visibleWeekday then
|
||||
UI.button
|
||||
[ class "py-5 px-3"
|
||||
, onClick
|
||||
(State.ToggleHabit
|
||||
(weekdayLabelFor todaysWeekday)
|
||||
label
|
||||
)
|
||||
]
|
||||
[ span
|
||||
[ Utils.class
|
||||
[ Always "text-white pt-1 px-2 rounded"
|
||||
, If isCompleted "bg-gray-400" "bg-blue-500"
|
||||
]
|
||||
]
|
||||
[ text (String.fromInt minutesDuration ++ " mins") ]
|
||||
, p
|
||||
[ Utils.class
|
||||
[ Always "inline pl-3"
|
||||
, When isCompleted "line-through text-gray-400"
|
||||
]
|
||||
]
|
||||
[ text label ]
|
||||
]
|
||||
|
||||
else
|
||||
UI.button
|
||||
[ class "py-5 px-3 cursor-not-allowed"
|
||||
, onClick State.DoNothing
|
||||
]
|
||||
[ text label ]
|
||||
]
|
||||
)
|
||||
)
|
||||
, footer [ class "bg-white text-sm text-center text-gray-500 fixed bottom-0 left-0 w-full py-4" ]
|
||||
[ p [] [ text "This app is brought to you by William Carroll." ]
|
||||
, p [] [ text "Client: Elm; Server: n/a" ]
|
||||
]
|
||||
]
|
||||
|
||||
( _, _ ) ->
|
||||
p [] [ text "Unable to display habits because we do not know what day of the week it is." ]
|
||||
29
users/wpcarro/website/habit-screens/src/Main.elm
Normal file
29
users/wpcarro/website/habit-screens/src/Main.elm
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
module Main exposing (main)
|
||||
|
||||
import Browser
|
||||
import Habits
|
||||
import Html exposing (..)
|
||||
import State
|
||||
import Time
|
||||
|
||||
|
||||
subscriptions : State.Model -> Sub State.Msg
|
||||
subscriptions model =
|
||||
-- once per minute
|
||||
Time.every (1000 * 60) (\_ -> State.MaybeAdjustWeekday)
|
||||
|
||||
|
||||
view : State.Model -> Html State.Msg
|
||||
view model =
|
||||
case model.view of
|
||||
State.Habits ->
|
||||
Habits.render model
|
||||
|
||||
|
||||
main =
|
||||
Browser.element
|
||||
{ init = \() -> State.init
|
||||
, subscriptions = subscriptions
|
||||
, update = State.update
|
||||
, view = view
|
||||
}
|
||||
195
users/wpcarro/website/habit-screens/src/State.elm
Normal file
195
users/wpcarro/website/habit-screens/src/State.elm
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
module State exposing (..)
|
||||
|
||||
import Date exposing (Date)
|
||||
import Set exposing (Set)
|
||||
import Task
|
||||
import Time exposing (Weekday(..))
|
||||
|
||||
|
||||
type alias WeekdayLabel =
|
||||
String
|
||||
|
||||
|
||||
type alias HabitLabel =
|
||||
String
|
||||
|
||||
|
||||
type Msg
|
||||
= DoNothing
|
||||
| SetView View
|
||||
| ReceiveDate Date
|
||||
| ToggleHabit WeekdayLabel HabitLabel
|
||||
| MaybeAdjustWeekday
|
||||
| ViewToday
|
||||
| ViewPrevious
|
||||
| ViewNext
|
||||
| ClearAll
|
||||
| ToggleMorning
|
||||
| ToggleEvening
|
||||
|
||||
|
||||
type View
|
||||
= Habits
|
||||
|
||||
|
||||
type HabitType
|
||||
= Morning
|
||||
| Evening
|
||||
| DayOfWeek
|
||||
| Payday
|
||||
| FirstOfTheMonth
|
||||
| FirstOfTheYear
|
||||
|
||||
|
||||
type alias Habit =
|
||||
{ label : HabitLabel
|
||||
, habitType : HabitType
|
||||
, minutesDuration : Int
|
||||
}
|
||||
|
||||
|
||||
type alias CompletedHabits =
|
||||
Set ( WeekdayLabel, HabitLabel )
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ isLoading : Bool
|
||||
, view : View
|
||||
, today : Maybe Date
|
||||
, completed : CompletedHabits
|
||||
, visibleDayOfWeek : Maybe Weekday
|
||||
, includeMorning : Bool
|
||||
, includeEvening : Bool
|
||||
}
|
||||
|
||||
|
||||
previousDay : Weekday -> Weekday
|
||||
previousDay weekday =
|
||||
case weekday of
|
||||
Mon ->
|
||||
Sun
|
||||
|
||||
Tue ->
|
||||
Mon
|
||||
|
||||
Wed ->
|
||||
Tue
|
||||
|
||||
Thu ->
|
||||
Wed
|
||||
|
||||
Fri ->
|
||||
Thu
|
||||
|
||||
Sat ->
|
||||
Fri
|
||||
|
||||
Sun ->
|
||||
Sat
|
||||
|
||||
|
||||
nextDay : Weekday -> Weekday
|
||||
nextDay weekday =
|
||||
case weekday of
|
||||
Mon ->
|
||||
Tue
|
||||
|
||||
Tue ->
|
||||
Wed
|
||||
|
||||
Wed ->
|
||||
Thu
|
||||
|
||||
Thu ->
|
||||
Fri
|
||||
|
||||
Fri ->
|
||||
Sat
|
||||
|
||||
Sat ->
|
||||
Sun
|
||||
|
||||
Sun ->
|
||||
Mon
|
||||
|
||||
|
||||
{-| The initial state for the application.
|
||||
-}
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( { isLoading = False
|
||||
, view = Habits
|
||||
, today = Nothing
|
||||
, completed = Set.empty
|
||||
, visibleDayOfWeek = Nothing
|
||||
, includeMorning = True
|
||||
, includeEvening = True
|
||||
}
|
||||
, Date.today |> Task.perform ReceiveDate
|
||||
)
|
||||
|
||||
|
||||
{-| Now that we have state, we need a function to change the state.
|
||||
-}
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg ({ today, visibleDayOfWeek, completed } as model) =
|
||||
case msg of
|
||||
DoNothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SetView x ->
|
||||
( { model
|
||||
| view = x
|
||||
, isLoading = True
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ReceiveDate x ->
|
||||
( { model
|
||||
| today = Just x
|
||||
, visibleDayOfWeek = Just (Date.weekday x)
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ToggleHabit weekdayLabel habitLabel ->
|
||||
( { model
|
||||
| completed =
|
||||
if Set.member ( weekdayLabel, habitLabel ) completed then
|
||||
Set.remove ( weekdayLabel, habitLabel ) completed
|
||||
|
||||
else
|
||||
Set.insert ( weekdayLabel, habitLabel ) completed
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
MaybeAdjustWeekday ->
|
||||
( model, Date.today |> Task.perform ReceiveDate )
|
||||
|
||||
ViewToday ->
|
||||
( { model | visibleDayOfWeek = today |> Maybe.map Date.weekday }, Cmd.none )
|
||||
|
||||
ViewPrevious ->
|
||||
( { model
|
||||
| visibleDayOfWeek = visibleDayOfWeek |> Maybe.map previousDay
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ViewNext ->
|
||||
( { model
|
||||
| visibleDayOfWeek = visibleDayOfWeek |> Maybe.map nextDay
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ClearAll ->
|
||||
( { model | completed = Set.empty }, Cmd.none )
|
||||
|
||||
ToggleMorning ->
|
||||
( { model | includeMorning = not model.includeMorning }, Cmd.none )
|
||||
|
||||
ToggleEvening ->
|
||||
( { model | includeEvening = not model.includeEvening }, Cmd.none )
|
||||
9
users/wpcarro/website/habit-screens/src/UI.elm
Normal file
9
users/wpcarro/website/habit-screens/src/UI.elm
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
module UI exposing (..)
|
||||
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
|
||||
|
||||
button : List (Attribute msg) -> List (Html msg) -> Html msg
|
||||
button attrs children =
|
||||
Html.button ([ class "focus:outline-none" ] ++ attrs) children
|
||||
37
users/wpcarro/website/habit-screens/src/Utils.elm
Normal file
37
users/wpcarro/website/habit-screens/src/Utils.elm
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
module Utils exposing (..)
|
||||
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Maybe.Extra
|
||||
|
||||
|
||||
type Strategy
|
||||
= Always String
|
||||
| When Bool String
|
||||
| If Bool String String
|
||||
|
||||
|
||||
class : List Strategy -> Attribute msg
|
||||
class classes =
|
||||
classes
|
||||
|> List.map
|
||||
(\strategy ->
|
||||
case strategy of
|
||||
Always x ->
|
||||
Just x
|
||||
|
||||
When True x ->
|
||||
Just x
|
||||
|
||||
When False _ ->
|
||||
Nothing
|
||||
|
||||
If True x _ ->
|
||||
Just x
|
||||
|
||||
If False _ x ->
|
||||
Just x
|
||||
)
|
||||
|> Maybe.Extra.values
|
||||
|> String.join " "
|
||||
|> Html.Attributes.class
|
||||
44
users/wpcarro/website/index.html
Normal file
44
users/wpcarro/website/index.html
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>wpcarro.dev</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Sitemap</h1>
|
||||
|
||||
<ul>
|
||||
<li>Documents</li>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/habits">Habits</a>
|
||||
</li>
|
||||
</ul>
|
||||
<li>Other</li>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://blog.wpcarro.dev">blog.wpcarro.dev</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://learn.wpcarro.dev">learn.wpcarro.dev</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://sandbox.wpcarro.dev">sandbox.wpcarro.dev</a>
|
||||
</li>
|
||||
</ul>
|
||||
<li>Social</li>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://twitter.com/wpcarro">Twitter</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/wpcarro">Github</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://linkedin.com/in/williampatrickcarroll">LinkedIn</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
4
users/wpcarro/website/learn/README.md
Normal file
4
users/wpcarro/website/learn/README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Learn
|
||||
|
||||
Hosting the content for my website `learn.wpcarro.dev`, where I advertise
|
||||
teaching others how to program.
|
||||
10
users/wpcarro/website/learn/default.nix
Normal file
10
users/wpcarro/website/learn/default.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "learn.wpcarro.dev";
|
||||
src = ./static;
|
||||
buildPhase = ''
|
||||
cp -R $src $out
|
||||
'';
|
||||
dontInstall = true;
|
||||
}
|
||||
102
users/wpcarro/website/learn/static/index.html
Normal file
102
users/wpcarro/website/learn/static/index.html
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Learn to code" />
|
||||
<link
|
||||
href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Learn to code</title>
|
||||
</head>
|
||||
<body class="font-serif container max-w-2xl mx-auto px-8">
|
||||
<h1 class="text-5xl text-center mt-12 mb-8">I can teach you to code.</h1>
|
||||
<div class="my-4">
|
||||
<p class="leading-relaxed mb-4">
|
||||
My name is William. I have programmed as a professional for five years
|
||||
and as a hobbyist for ten years. I am currently an engineer at Google,
|
||||
but I have worked at small start-ups and medium-sized corporations.
|
||||
</p>
|
||||
<p class="leading-relaxed mb-4">
|
||||
Whether you have never written a single line of code, or you know how to
|
||||
code and you are interested in going deeper, I can teach you a variety
|
||||
of skills that professional software engineers use to succeed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<h2 class="text-3xl">Why coding?</h2>
|
||||
<p class="leading-relaxed mb-4">
|
||||
Are you interested in creating your own website? Perhaps you would like
|
||||
to make your own video game. Maybe you like the notion of automating the
|
||||
boring things in your life with code, but you do not know where to
|
||||
start.
|
||||
</p>
|
||||
<p class="leading-relaxed mb-4">
|
||||
Coding is the most creative outlet in my life, and I say that as a
|
||||
musician and a former architecture student. I know many people who want
|
||||
to learn how to code, who know they can learn online, but they still
|
||||
cannot code. Together we will overcome this barrier to entry. I will
|
||||
teach you how to code, and then I will teach you how to take yourself
|
||||
the rest of the way. <b>You will be coding from day one.</b>
|
||||
</p>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<h2 class="text-3xl">Pricing</h2>
|
||||
<p class="leading-relaxed mb-4">
|
||||
I charge <bold class="font-bold">£50</bold> per hour for video lessons
|
||||
and <bold class="font-bold">£100</bold> per hour for in-person sessions.
|
||||
I am currently based in South Kensington, London.
|
||||
</p>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<h2 class="text-3xl">Contact</h2>
|
||||
<p class="leading-relaxed mb-4">
|
||||
Whether you want to sign-up or simply want to learn more, send me an
|
||||
email at
|
||||
<a
|
||||
href="mailto:wpcarro@gmail.com"
|
||||
class="font-bold text-blue-600 hover:underline"
|
||||
>wpcarro@gmail.com</a
|
||||
>.
|
||||
</p>
|
||||
<p class="text-center my-8">Why delay? <em>Start today.</em></p>
|
||||
</div>
|
||||
<footer class="mb-8 lg:flex">
|
||||
<a
|
||||
class="block py-2 lg:w-1/4 text-center hover:underline"
|
||||
href="https://blog.wpcarro.dev"
|
||||
>Blog</a
|
||||
>
|
||||
<a
|
||||
class="block py-2 lg:w-1/4 text-center hover:underline"
|
||||
href="https://linkedin.com/in/williampatrickcarroll"
|
||||
>LinkedIn</a
|
||||
>
|
||||
<a
|
||||
class="block py-2 lg:w-1/4 text-center hover:underline"
|
||||
href="https://twitter.com/wpcarro"
|
||||
>Twitter</a
|
||||
>
|
||||
<a
|
||||
class="block py-2 lg:w-1/4 text-center hover:underline"
|
||||
href="https://github.com/wpcarro"
|
||||
>Github</a
|
||||
>
|
||||
</footer>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=UA-160226702-1"
|
||||
></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag("js", new Date());
|
||||
|
||||
gtag("config", "UA-160226702-1");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
4
users/wpcarro/website/sandbox/contentful/.envrc
Normal file
4
users/wpcarro/website/sandbox/contentful/.envrc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
source_up
|
||||
use_nix
|
||||
export CONTENTFUL_SPACE_ID="$(jq -j '.contentful | .spaceId' < ~/briefcase/secrets.json)"
|
||||
export CONTENTFUL_ACCESS_TOKEN="$(jq -j '.contentful | .accessToken' < ~/briefcase/secrets.json)"
|
||||
2
users/wpcarro/website/sandbox/contentful/.gitignore
vendored
Normal file
2
users/wpcarro/website/sandbox/contentful/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.cache
|
||||
dist
|
||||
18
users/wpcarro/website/sandbox/contentful/README.md
Normal file
18
users/wpcarro/website/sandbox/contentful/README.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Contentful
|
||||
|
||||
I have not used a CMS in a few years. I learned about Contentful from a
|
||||
Gatsby.js tutorial, and I wanted to learn more; I created a Contentful account,
|
||||
and I'm experimenting with the data here.
|
||||
|
||||
## Developing
|
||||
|
||||
```shell
|
||||
$ nix-shell
|
||||
$ yarn run dev
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
```shell
|
||||
$ nix-build
|
||||
```
|
||||
19
users/wpcarro/website/sandbox/contentful/default.nix
Normal file
19
users/wpcarro/website/sandbox/contentful/default.nix
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "ideal-website";
|
||||
src = builtins.path { path = ./.; name = "contentful"; };
|
||||
buildInputs = with pkgs; [
|
||||
nodejs
|
||||
# Exposes lscpu for parcel.js
|
||||
utillinux
|
||||
];
|
||||
# parcel.js needs number of CPUs
|
||||
PARCEL_WORKERS = "1";
|
||||
buildPhase = ''
|
||||
npx parcel build index.html
|
||||
'';
|
||||
installPhase = ''
|
||||
mv dist $out
|
||||
'';
|
||||
}
|
||||
26
users/wpcarro/website/sandbox/contentful/package.json
Normal file
26
users/wpcarro/website/sandbox/contentful/package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "tailwindcss",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "npx parcel src/index.html & npx tsc --watch --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^13.9.3",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"tailwindcss": "^1.2.0",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.2.5",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/react-redux": "^7.1.7",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"contentful": "^7.14.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-router-dom": "^5.1.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
const tailwindcss = require("tailwindcss");
|
||||
|
||||
module.exports = {
|
||||
plugins: [tailwindcss("./tailwind.config.js")],
|
||||
};
|
||||
9
users/wpcarro/website/sandbox/contentful/shell.nix
Normal file
9
users/wpcarro/website/sandbox/contentful/shell.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
let
|
||||
briefcase = import <briefcase> {};
|
||||
pkgs = briefcase.third_party.pkgs;
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
nodejs
|
||||
yarn
|
||||
];
|
||||
}
|
||||
49
users/wpcarro/website/sandbox/contentful/src/App.tsx
Normal file
49
users/wpcarro/website/sandbox/contentful/src/App.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { actions, useTypedSelector } from "./store";
|
||||
import { Link } from "react-router-dom";
|
||||
import { getClient } from "./contentful";
|
||||
import type { Book } from "./store";
|
||||
|
||||
const App: React.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { isLoading, books } = useTypedSelector((state) => ({
|
||||
isLoading: state.isLoading,
|
||||
books: state.books,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const entries = await getClient().getEntries();
|
||||
const books = entries.items.map((x) => x.fields) as Book[];
|
||||
|
||||
dispatch(actions.setBooks(books));
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<div className="container mx-auto">
|
||||
<h1 className="py-6 text-2xl">Books</h1>
|
||||
<ul>
|
||||
{books.map((book) => (
|
||||
<li key={book.title} className="py-3">
|
||||
<p>
|
||||
<span className="font-bold pr-3">{book.title}</span>
|
||||
<span className="text-gray-600">{book.author}</span>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
27
users/wpcarro/website/sandbox/contentful/src/contentful.ts
Normal file
27
users/wpcarro/website/sandbox/contentful/src/contentful.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { createClient } from "contentful";
|
||||
import type { ContentfulClientApi } from "contentful";
|
||||
|
||||
const space = process.env.CONTENTFUL_SPACE_ID;
|
||||
const accessToken = process.env.CONTENTFUL_ACCESS_TOKEN;
|
||||
|
||||
let client: ContentfulClientApi;
|
||||
|
||||
// Idempotent way to get a reference to the Contentful client.
|
||||
export const getClient = (): ContentfulClientApi => {
|
||||
if (typeof client !== "undefined") {
|
||||
return client;
|
||||
} else {
|
||||
if (typeof space === "string" && typeof accessToken === "string") {
|
||||
let client = createClient({
|
||||
space,
|
||||
accessToken,
|
||||
});
|
||||
|
||||
return client;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Please set CONTENTFUL_SPACE_ID and CONTENTFUL_ACCESS_TOKEN"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
3
users/wpcarro/website/sandbox/contentful/src/index.css
Normal file
3
users/wpcarro/website/sandbox/contentful/src/index.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
11
users/wpcarro/website/sandbox/contentful/src/index.html
Normal file
11
users/wpcarro/website/sandbox/contentful/src/index.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" href="./index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="mount"></div>
|
||||
<script src="./index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
12
users/wpcarro/website/sandbox/contentful/src/index.tsx
Normal file
12
users/wpcarro/website/sandbox/contentful/src/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
import { Provider } from "react-redux";
|
||||
import store from "./store";
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById("mount")
|
||||
);
|
||||
36
users/wpcarro/website/sandbox/contentful/src/store.ts
Normal file
36
users/wpcarro/website/sandbox/contentful/src/store.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { createSlice, configureStore, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { useSelector, TypedUseSelectorHook } from "react-redux";
|
||||
|
||||
export interface Book {
|
||||
title: string;
|
||||
author: string;
|
||||
// TODO(wpcarro): Prefer datetime type here.
|
||||
publicationDate: string;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
isLoading: boolean;
|
||||
books: Book[];
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
isLoading: true,
|
||||
books: [],
|
||||
};
|
||||
|
||||
export const { actions, reducer } = createSlice({
|
||||
name: "application",
|
||||
initialState,
|
||||
reducers: {
|
||||
toggleIsLoading: (state) => ({ ...state, isLoading: !state.isLoading }),
|
||||
setBooks: (state, action) => ({ ...state, books: action.payload }),
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Defining and consuming this allows us to avoid annotating State in all of our
|
||||
* selectors.
|
||||
*/
|
||||
export const useTypedSelector: TypedUseSelectorHook<State> = useSelector;
|
||||
|
||||
export default configureStore({ reducer });
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [],
|
||||
};
|
||||
19
users/wpcarro/website/sandbox/contentful/tsconfig.json
Normal file
19
users/wpcarro/website/sandbox/contentful/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
5717
users/wpcarro/website/sandbox/contentful/yarn.lock
Normal file
5717
users/wpcarro/website/sandbox/contentful/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
16
users/wpcarro/website/sandbox/covid-uk/default.nix.ignore
Normal file
16
users/wpcarro/website/sandbox/covid-uk/default.nix.ignore
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "covid-uk";
|
||||
buildInputs = [];
|
||||
src = builtins.path { path = ./.; name = "covid-uk"; };
|
||||
# TODO(wpcarro): Need to run `yarn install` somehow.
|
||||
# TODO(wpcarro): Need to run `npx tailwindcss build styles.css -o output.css`.
|
||||
buildPhase = ''
|
||||
mkdir -p $out
|
||||
mkdir -p $out/node_modules/chart.js/dist
|
||||
cp $src/node_modules/chart.js/dist/Chart.bundle.min.js $out/node_modules/chart.js/dist
|
||||
cp $src/index.html $src/output.css $out
|
||||
'';
|
||||
dontInstall = true;
|
||||
}
|
||||
99
users/wpcarro/website/sandbox/covid-uk/index.html
Normal file
99
users/wpcarro/website/sandbox/covid-uk/index.html
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>COVID-19 UK</title>
|
||||
<link rel="stylesheet" href="output.css">
|
||||
</head>
|
||||
<body class="container mx-auto py-10">
|
||||
<div>
|
||||
<h1 class="text-center">COVID-19 in the UK</h1>
|
||||
<p>
|
||||
Up until recently, I used a couple of resources (i.e.
|
||||
<a href="https://multimedia.scmp.com/infographics/news/china/article/3047038/wuhan-virus/index.html">one</a>,
|
||||
<a href="https://www.worldometers.info/coronavirus/">two</a>) for tracking
|
||||
an updated number of confirmed covid-19 cases.
|
||||
</p>
|
||||
<p>
|
||||
Given the high speed at which the virus is spreading, I was having a
|
||||
difficult time intuiting the shape of this growth. For example if today
|
||||
the total number of confirmed cases for covid-19 in the UK was 500, I
|
||||
could not remember if yesterday it was 450, 400, or 200.
|
||||
</p>
|
||||
<p>
|
||||
Thankfully someone is <a
|
||||
href="https://github.com/pomber/covid19">publishing this data</a> as a
|
||||
timeseries database. I am currently living in London, so I decided to
|
||||
chart the <u>daily number of confirmed covid-19 cases in the UK</u> to
|
||||
better understand what is happening.
|
||||
</p>
|
||||
</div>
|
||||
<canvas id="myChart" class="py-12"></canvas>
|
||||
<script src="./node_modules/chart.js/dist/Chart.bundle.min.js"></script>
|
||||
<script>
|
||||
var timeseries =
|
||||
fetch('https://pomber.github.io/covid19/timeseries.json')
|
||||
.then(res => res.json())
|
||||
.then(createChart);
|
||||
|
||||
function createChart(data) {
|
||||
var uk = data["United Kingdom"];
|
||||
var data = uk.map(x => x["confirmed"]);
|
||||
var labels = uk.map(x => x["date"]);
|
||||
|
||||
var ctx = document.getElementById('myChart').getContext('2d');
|
||||
var myChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Number of confirmed COVID-19 cases in the U.K.',
|
||||
data: data,
|
||||
backgroundColor: 'rgba(255, 0, 100, 0.2)',
|
||||
borderWidth: 3
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<div>
|
||||
<h2 class="text-center">Back of the envelope predictions</h2>
|
||||
<p>
|
||||
From what I have read, a population where 60% of its constituents have
|
||||
been infected with covid-19 and have recovered is said to have "herd
|
||||
immunity". Once a population has herd immunity, the rate at which the
|
||||
virus spreads decreases.
|
||||
</p>
|
||||
<p>
|
||||
Roughly 60M people live in the UK; 60% of 60M is around 40M. Before a
|
||||
population reaches "herd immunity", the total number of <em>true
|
||||
covid-19 cases</em> <u>doubles every five days</u>. Therefore in <u>fifty
|
||||
days</u> you might expect the number of true cases to be <u>1000x
|
||||
larger</u> than what it is today.
|
||||
</p>
|
||||
<p>
|
||||
So if you think the total number of <em>true covid-19 cases</em>
|
||||
<u>today</u> is 40,000 then you might expect the rate of growth to slow
|
||||
down in a little less than two months.
|
||||
</p>
|
||||
<p>
|
||||
Thank you for reading.
|
||||
</p>
|
||||
</div>
|
||||
<footer class="pt-5 mb-8 lg:flex">
|
||||
<a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://learn.wpcarro.dev">Learn</a>
|
||||
<a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://blog.wpcarro.dev">Blog</a>
|
||||
<a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://twitter.com/wpcarro">Twitter</a>
|
||||
<a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://github.com/wpcarro">Github</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
16
users/wpcarro/website/sandbox/covid-uk/package.json
Normal file
16
users/wpcarro/website/sandbox/covid-uk/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "covid-uk",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chart.js": "^2.9.3",
|
||||
"tailwindcss": "^1.2.0"
|
||||
}
|
||||
}
|
||||
9
users/wpcarro/website/sandbox/covid-uk/shell.nix
Normal file
9
users/wpcarro/website/sandbox/covid-uk/shell.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
let
|
||||
briefcase = import <briefcase> {};
|
||||
pkgs = briefcase.third_party.pkgs;
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
yarn
|
||||
nodejs
|
||||
];
|
||||
}
|
||||
28
users/wpcarro/website/sandbox/covid-uk/styles.css
Normal file
28
users/wpcarro/website/sandbox/covid-uk/styles.css
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
@tailwind base;
|
||||
|
||||
body {
|
||||
@apply font-mono;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-3xl mb-5 font-bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-2xl mb-5 font-bold;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-xl mb-5 font-bold;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-blue-600 underline;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply mt-2 mb-5;
|
||||
}
|
||||
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [],
|
||||
}
|
||||
542
users/wpcarro/website/sandbox/covid-uk/yarn.lock
Normal file
542
users/wpcarro/website/sandbox/covid-uk/yarn.lock
Normal file
|
|
@ -0,0 +1,542 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/color-name@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||
|
||||
acorn-node@^1.6.1:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
|
||||
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
|
||||
dependencies:
|
||||
acorn "^7.0.0"
|
||||
acorn-walk "^7.0.0"
|
||||
xtend "^4.0.2"
|
||||
|
||||
acorn-walk@^7.0.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e"
|
||||
integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==
|
||||
|
||||
acorn@^7.0.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf"
|
||||
integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
ansi-styles@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
|
||||
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
|
||||
dependencies:
|
||||
"@types/color-name" "^1.1.1"
|
||||
color-convert "^2.0.1"
|
||||
|
||||
autoprefixer@^9.4.5:
|
||||
version "9.7.4"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378"
|
||||
integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g==
|
||||
dependencies:
|
||||
browserslist "^4.8.3"
|
||||
caniuse-lite "^1.0.30001020"
|
||||
chalk "^2.4.2"
|
||||
normalize-range "^0.1.2"
|
||||
num2fraction "^1.2.2"
|
||||
postcss "^7.0.26"
|
||||
postcss-value-parser "^4.0.2"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
browserslist@^4.8.3:
|
||||
version "4.10.0"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.10.0.tgz#f179737913eaf0d2b98e4926ac1ca6a15cbcc6a9"
|
||||
integrity sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001035"
|
||||
electron-to-chromium "^1.3.378"
|
||||
node-releases "^1.1.52"
|
||||
pkg-up "^3.1.0"
|
||||
|
||||
bytes@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||
|
||||
camelcase-css@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
|
||||
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
||||
|
||||
caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001035:
|
||||
version "1.0.30001035"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz#2bb53b8aa4716b2ed08e088d4dc816a5fe089a1e"
|
||||
integrity sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ==
|
||||
|
||||
chalk@^2.4.1, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chart.js@^2.9.3:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7"
|
||||
integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==
|
||||
dependencies:
|
||||
chartjs-color "^2.1.0"
|
||||
moment "^2.10.2"
|
||||
|
||||
chartjs-color-string@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
|
||||
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
|
||||
dependencies:
|
||||
color-name "^1.0.0"
|
||||
|
||||
chartjs-color@^2.1.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0"
|
||||
integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==
|
||||
dependencies:
|
||||
chartjs-color-string "^0.6.0"
|
||||
color-convert "^1.9.3"
|
||||
|
||||
color-convert@^1.9.0, color-convert@^1.9.3:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-convert@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
|
||||
dependencies:
|
||||
color-name "~1.1.4"
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
color-name@^1.0.0, color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
css-unit-converter@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996"
|
||||
integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||
|
||||
defined@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
|
||||
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
|
||||
|
||||
detective@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b"
|
||||
integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==
|
||||
dependencies:
|
||||
acorn-node "^1.6.1"
|
||||
defined "^1.0.0"
|
||||
minimist "^1.1.1"
|
||||
|
||||
electron-to-chromium@^1.3.378:
|
||||
version "1.3.379"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.379.tgz#81dc5e82a3e72bbb830d93e15bc35eda2bbc910e"
|
||||
integrity sha512-NK9DBBYEBb5f9D7zXI0hiE941gq3wkBeQmXs1ingigA/jnTg5mhwY2Z5egwA+ZI8OLGKCx0h1Cl8/xeuIBuLlg==
|
||||
|
||||
escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
find-up@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
|
||||
integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
|
||||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
fs-extra@^8.0.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
||||
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
glob@^7.1.2:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
|
||||
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has-flag@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
indexes-of@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
|
||||
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
||||
dependencies:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
locate-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
|
||||
integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
|
||||
dependencies:
|
||||
p-locate "^3.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
lodash.toarray@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
|
||||
integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE=
|
||||
|
||||
lodash@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.1.1:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
moment@^2.10.2:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||
|
||||
node-emoji@^1.8.1:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da"
|
||||
integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==
|
||||
dependencies:
|
||||
lodash.toarray "^4.4.0"
|
||||
|
||||
node-releases@^1.1.52:
|
||||
version "1.1.52"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9"
|
||||
integrity sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ==
|
||||
dependencies:
|
||||
semver "^6.3.0"
|
||||
|
||||
normalize-range@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
||||
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
|
||||
|
||||
normalize.css@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3"
|
||||
integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==
|
||||
|
||||
num2fraction@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
|
||||
integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
|
||||
|
||||
object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
p-limit@^2.0.0:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
|
||||
integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==
|
||||
dependencies:
|
||||
p-try "^2.0.0"
|
||||
|
||||
p-locate@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
|
||||
integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
|
||||
dependencies:
|
||||
p-limit "^2.0.0"
|
||||
|
||||
p-try@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
path-exists@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
|
||||
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
|
||||
|
||||
path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
path-parse@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
||||
|
||||
pkg-up@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
|
||||
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
|
||||
dependencies:
|
||||
find-up "^3.0.0"
|
||||
|
||||
postcss-functions@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e"
|
||||
integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4=
|
||||
dependencies:
|
||||
glob "^7.1.2"
|
||||
object-assign "^4.1.1"
|
||||
postcss "^6.0.9"
|
||||
postcss-value-parser "^3.3.0"
|
||||
|
||||
postcss-js@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-2.0.3.tgz#a96f0f23ff3d08cec7dc5b11bf11c5f8077cdab9"
|
||||
integrity sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w==
|
||||
dependencies:
|
||||
camelcase-css "^2.0.1"
|
||||
postcss "^7.0.18"
|
||||
|
||||
postcss-nested@^4.1.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-4.2.1.tgz#4bc2e5b35e3b1e481ff81e23b700da7f82a8b248"
|
||||
integrity sha512-AMayXX8tS0HCp4O4lolp4ygj9wBn32DJWXvG6gCv+ZvJrEa00GUxJcJEEzMh87BIe6FrWdYkpR2cuyqHKrxmXw==
|
||||
dependencies:
|
||||
postcss "^7.0.21"
|
||||
postcss-selector-parser "^6.0.2"
|
||||
|
||||
postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
|
||||
integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
|
||||
postcss-value-parser@^3.3.0:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
|
||||
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
|
||||
|
||||
postcss-value-parser@^4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d"
|
||||
integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==
|
||||
|
||||
postcss@^6.0.9:
|
||||
version "6.0.23"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
|
||||
integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
source-map "^0.6.1"
|
||||
supports-color "^5.4.0"
|
||||
|
||||
postcss@^7.0.11, postcss@^7.0.18, postcss@^7.0.21, postcss@^7.0.26:
|
||||
version "7.0.27"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9"
|
||||
integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
source-map "^0.6.1"
|
||||
supports-color "^6.1.0"
|
||||
|
||||
pretty-hrtime@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
|
||||
integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
|
||||
|
||||
reduce-css-calc@^2.1.6:
|
||||
version "2.1.7"
|
||||
resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2"
|
||||
integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA==
|
||||
dependencies:
|
||||
css-unit-converter "^1.1.1"
|
||||
postcss-value-parser "^3.3.0"
|
||||
|
||||
resolve@^1.14.2:
|
||||
version "1.15.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"
|
||||
integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==
|
||||
dependencies:
|
||||
path-parse "^1.0.6"
|
||||
|
||||
semver@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
supports-color@^5.3.0, supports-color@^5.4.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
|
||||
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
tailwindcss@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.2.0.tgz#5df317cebac4f3131f275d258a39da1ba3a0f291"
|
||||
integrity sha512-CKvY0ytB3ze5qvynG7qv4XSpQtFNGPbu9pUn8qFdkqgD8Yo/vGss8mhzbqls44YCXTl4G62p3qVZBj45qrd6FQ==
|
||||
dependencies:
|
||||
autoprefixer "^9.4.5"
|
||||
bytes "^3.0.0"
|
||||
chalk "^3.0.0"
|
||||
detective "^5.2.0"
|
||||
fs-extra "^8.0.0"
|
||||
lodash "^4.17.15"
|
||||
node-emoji "^1.8.1"
|
||||
normalize.css "^8.0.1"
|
||||
postcss "^7.0.11"
|
||||
postcss-functions "^3.0.0"
|
||||
postcss-js "^2.0.0"
|
||||
postcss-nested "^4.1.1"
|
||||
postcss-selector-parser "^6.0.0"
|
||||
pretty-hrtime "^1.0.3"
|
||||
reduce-css-calc "^2.1.6"
|
||||
resolve "^1.14.2"
|
||||
|
||||
uniq@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
||||
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
|
||||
|
||||
universalify@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
xtend@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
13
users/wpcarro/website/sandbox/default.nix.ignore
Normal file
13
users/wpcarro/website/sandbox/default.nix.ignore
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{ pkgs, briefcase, ... }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "covid-uk";
|
||||
buildInputs = [];
|
||||
src = builtins.path { path = ./.; name = "sandbox"; };
|
||||
buildPhase = ''
|
||||
mkdir -p $out
|
||||
cp $src/index.html $out
|
||||
cp -r ${briefcase.website.sandbox.covid-uk} $out/covid-uk
|
||||
'';
|
||||
dontInstall = true;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# Github Issues Service (GIS)
|
||||
|
||||
> 'Cause I got issues. But you got 'em too...
|
||||
> - [Issues by Julia Michaels][issues]
|
||||
|
||||
You have a website and your users want to request features or report bugs. How
|
||||
do they do this?
|
||||
|
||||
Our robot, GIS, can help you. GIS adds a widget to your website that allows
|
||||
users to easily request features and report bugs.
|
||||
|
||||
## Getting Started
|
||||
|
||||
If Github is hosting your website's source code, you're ready to start using
|
||||
GIS. GIS works with public and private repositories.
|
||||
|
||||
Let's adopt Github's notion of "issues" to group feature requests and bug
|
||||
reports together. When users click the GIS widget to create an issue, GIS
|
||||
displays a modal form that the user completes. When the user submits the form,
|
||||
GIS creates an issue on your Github repository. Now your team can use all of
|
||||
Github's rich issue-tracking tools to manage your issues.
|
||||
|
||||
## Installation
|
||||
|
||||
To add GIS to your website, register your Github repository with us and we'll
|
||||
give you a snippet to add to your website's HTML. It's that simple.
|
||||
|
||||
[issues]: https://www.youtube.com/watch?v=9Ke4480MicU
|
||||
15
users/wpcarro/website/sandbox/index.html
Normal file
15
users/wpcarro/website/sandbox/index.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>sandbox.wpcarro.dev</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Projects</h1>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/covid-uk">COVID-19 in the UK</a>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue