diff --git a/exercises/anagram.livemd b/exercises/anagram.livemd index 99d40a44d..af3db1a5c 100644 --- a/exercises/anagram.livemd +++ b/exercises/anagram.livemd @@ -67,6 +67,7 @@ defmodule Anagram do true """ def anagram?(string1, string2) do + String.to_charlist(string1) |> Enum.sort() == String.to_charlist(string2) |> Enum.sort() end @doc """ @@ -84,6 +85,7 @@ defmodule Anagram do [] """ def filter_anagrams(word_list, anagram) do + Enum.filter(word_list, fn word -> anagram?(word, anagram) end) end end ``` diff --git a/exercises/animal_generator.livemd b/exercises/animal_generator.livemd index 81d96f88c..12d9cc543 100644 --- a/exercises/animal_generator.livemd +++ b/exercises/animal_generator.livemd @@ -57,7 +57,13 @@ end Enter your solution below. ```elixir +names = ["Clifford", "Zoboomafoo", "Leonardo"] +animal_types = ["dog", "lemur", "turtle"] +ages = 1..14 +for name <- names, animal_type <- animal_types, age <- ages do + %{name: name, animal_type: animal_type, age: age} +end ``` ## Mark As Completed diff --git a/exercises/battle_map.livemd b/exercises/battle_map.livemd index 2a1b783fc..cfd4781cc 100644 --- a/exercises/battle_map.livemd +++ b/exercises/battle_map.livemd @@ -130,6 +130,22 @@ defprotocol Character do def can_attack?(character, origin, target) end +defimpl Character, for: Barbarian do + def can_attack?(_character, {x1, y1}, {x2, y2}) do + # can move +/- 2 spaces along the x axis -> horizontal + # can move +/- 2 spaces along the y axis -> vertical + # horizontal # vertical + abs(x2 - x1) <= 2 or abs(y2 - y1) <= 2 + end +end + +defimpl Character, for: Wizard do + def can_attack?(_character, {x1, y1}, {x2, y2}) do + # vertical/horizontal # diagonal + x1 == x2 || y1 == y2 || abs(x2 - x1) == abs(y2 - y1) + end +end + ExUnit.start(auto_run: false) defmodule CharacterTests do @@ -198,7 +214,22 @@ true Implement your custom character below. ```elixir +defmodule Archer do + defstruct [] +end +``` + +```elixir +defimpl Character, for: Archer do + def can_attack?(_character, {x1, y1}, {x2, y2}) do + # can move +/- 3 spaces along the x axis -> horizontal + # can move +/- 3 spaces along the y axis -> vertical + # horizontal # vertical + abs(x2 - x1) == 3 or abs(y2 - y1) == 3 + end +end +Character.can_attack?(%Archer{}, {4, 4}, {8, 8}) ``` ## Mark As Completed diff --git a/exercises/book_search.livemd b/exercises/book_search.livemd index a63beca18..9c76e8eaa 100644 --- a/exercises/book_search.livemd +++ b/exercises/book_search.livemd @@ -30,6 +30,7 @@ defmodule Book do iex> %Book{title: "My Book Title"} %Book{title: "My Book Title"} """ + defstruct [:title] @doc """ Search a list of Book structs. Search should match any book that includes the @@ -38,6 +39,7 @@ defmodule Book do ## Examples Include books that exactly match the search query. + # Map.get(map, :title) == query iex> book1 = %Book{title: "A"} iex> book2 = %Book{title: "B"} @@ -46,6 +48,8 @@ defmodule Book do [%Book{title: "A"}] Include books that partially match the search query. + # does the query match any letter of the title? + # use String.contains(book, query) iex> Book.search([%Book{title: "ABC"}], "A") [%Book{title: "ABC"}] @@ -54,15 +58,36 @@ defmodule Book do [%Book{title: "BAC"}] Search should be case insensitive. + # lowercase the input and title iex> Book.search([%Book{title: "ABC"}], "a") [%Book{title: "ABC"}] """ + + # def fuzzy_match(string, char) do + # mysplit = String.split(string, "", trim: true) + # Enum.filter(mysplit, fn element -> element == char end) && string + # end + def search(books, query) do + # books that match one letter + Enum.filter(books, fn book -> String.contains?(book, query) end) + + # books that exactly match + Enum.filter(books, fn book -> Map.get(book, :title) end) end end ``` +```elixir +book = "ABC" +query = "D" +String.contains?(book, query) + +test = %{mybook: "mytitle"} +Map.get(test, ) +``` + ## Mark As Completed diff --git a/exercises/caesar_cypher.livemd b/exercises/caesar_cypher.livemd index 91df9252d..2066ca8fc 100644 --- a/exercises/caesar_cypher.livemd +++ b/exercises/caesar_cypher.livemd @@ -82,13 +82,13 @@ defmodule CaesarCypher do ## Examples - iex> CaesarCypher.encode("abcdefghijklmnopqrstuvwxyz") - "bcdefghijklmnopqrstuvwxyza" + # iex> CaesarCypher.encode("abcdefghijklmnopqrstuvwxyz") + # "bcdefghijklmnopqrstuvwxyza" - Encoding should work on any string + # Encoding should work on any string - iex> CaesarCypher.encode("hello") - "ifmmp" + # iex> CaesarCypher.encode("hello") + # "ifmmp" """ def encode(string) do end @@ -99,14 +99,14 @@ defmodule CaesarCypher do ## Examples - iex> CaesarCypher.encode("abcdefghijklmnopqrstuvwxyz", 1) - "bcdefghijklmnopqrstuvwxyza" + # iex> CaesarCypher.encode("abcdefghijklmnopqrstuvwxyz", 1) + # "bcdefghijklmnopqrstuvwxyza" - iex> CaesarCypher.encode("abcdefghijklmnopqrstuvwxyz", 2) - "cdefghijklmnopqrstuvwxyzab" + # iex> CaesarCypher.encode("abcdefghijklmnopqrstuvwxyz", 2) + # "cdefghijklmnopqrstuvwxyzab" - iex> CaesarCypher.encode("abcdefghijklmnopqrstuvwxyz", 14) - "opqrstuvwxyzabcdefghijklmn" + # iex> CaesarCypher.encode("abcdefghijklmnopqrstuvwxyz", 14) + # "opqrstuvwxyzabcdefghijklmn" Encoding should work on any string. @@ -118,6 +118,25 @@ defmodule CaesarCypher do end ``` +```elixir +string = "abcdefghijklmnopqrstuvwxyz" +offset = 2 + +chars = String.to_charlist(string) + +Enum.map(chars, fn char -> char + 2 end) + +?z - offset + +# Enum.map(chars, fn char -> +# if char < 'z' - offset do +# 'z' +# else +# char + 2 +# end +# end) +``` + ## Mark As Completed diff --git a/exercises/counting_votes.livemd b/exercises/counting_votes.livemd index b7ca899b0..cb1e0c25e 100644 --- a/exercises/counting_votes.livemd +++ b/exercises/counting_votes.livemd @@ -80,6 +80,7 @@ defmodule Votes do 0 """ def count(votes, vote) do + Enum.count(Enum.filter(votes, fn item -> item == vote end)) end end ``` @@ -120,6 +121,10 @@ defmodule VoterTally do %{dog: 2, cat: 3, bird: 1} """ def tally(votes) do + dogs = Enum.count(Enum.filter(votes, fn item -> item == :dog end)) + cats = Enum.count(Enum.filter(votes, fn item -> item == :cat end)) + birds = Enum.count(Enum.filter(votes, fn item -> item == :bird end)) + %{dog: dogs, cat: cats, bird: birds} end end ``` diff --git a/exercises/custom_enum_with_reduce.livemd b/exercises/custom_enum_with_reduce.livemd index 108de363c..7bdee0bad 100644 --- a/exercises/custom_enum_with_reduce.livemd +++ b/exercises/custom_enum_with_reduce.livemd @@ -78,6 +78,11 @@ defmodule CustomEnum do [7, 6, 5, 4] """ def reverse(list) do + # put first element on head of new list + Enum.reduce(list, [], fn element, acc -> + # IO.inspect(binding()) + [element | acc] + end) end @doc """ @@ -92,6 +97,11 @@ defmodule CustomEnum do [true, true, true] """ def map(list, callback_function) do + # while list not empty apply fn to element + Enum.reduce(list, [], fn element, acc -> + [callback_function.(element) | acc] + end) + |> reverse() end @doc """ @@ -107,6 +117,15 @@ defmodule CustomEnum do ["2", "3"] """ def filter(list, callback_function) do + # add element to new list if callback_function(element) returns true + Enum.reduce(list, [], fn element, acc -> + if callback_function.(element) do + [element | acc] + else + acc + end + end) + |> reverse() end @doc """ @@ -117,10 +136,12 @@ defmodule CustomEnum do iex> CustomEnum.sum([1, 2, 3]) 6 - iex> CustomEnum.sum([1, 1, 1]) - 3 + # iex> CustomEnum.sum([1, 1, 1]) + # 3 """ def sum(list_of_integers) do + # add each int to accumulator and return accumulator + Enum.reduce(list_of_integers, 0, fn int, acc -> int + acc end) end @doc """ @@ -135,6 +156,8 @@ defmodule CustomEnum do "Hello, World!" """ def join(list_of_strings) do + # concat each string to acc string + Enum.reduce(list_of_strings, "", fn string, acc -> acc <> string end) end end ``` diff --git a/exercises/dominoes.livemd b/exercises/dominoes.livemd index aa3337209..ac4cc4f8c 100644 --- a/exercises/dominoes.livemd +++ b/exercises/dominoes.livemd @@ -97,7 +97,22 @@ Supervisor.start_link(children, strategy: :rest_for_one) Keep in mind, if you have already started a named process, the supervisor might crash when you attempt to start it again. Re-evaluate the cell after the livebook crashes to resolve this issue. ```elixir +children = [ + %{ + id: D1, + start: {Domino, :start_link, [[name: D1]]} + }, + %{ + id: D2, + start: {Domino, :start_link, [[name: D2]]} + }, + %{ + id: D3, + start: {Domino, :start_link, [[name: D3]]} + } +] +{:ok, pid} = Supervisor.start_link(children, strategy: :rest_for_one) ``` Send your dominos messages to ensure they are crashing in the correct order. They will log a message that demonstrates the `Domino.start_link/1` function was called again. @@ -116,7 +131,7 @@ Process.send(:domino_name, :fall, []) Test sending each `Domino` process a message individually. ```elixir - +Process.send(D3, :fall, []) ``` ## Mark As Completed diff --git a/exercises/drill-patternmatching-replace-nils.livemd b/exercises/drill-patternmatching-replace-nils.livemd index 3ae0a4a58..e34dcb7a3 100644 --- a/exercises/drill-patternmatching-replace-nils.livemd +++ b/exercises/drill-patternmatching-replace-nils.livemd @@ -63,9 +63,39 @@ defmodule ReplaceNils do @doc """ replace nil values in the first list with values from the second list in the same position. + + iex> input1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + iex> input2 = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] + iex> ReplaceNils.replace(input1, input2) + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + iex> input1 = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] + iex> input2 = [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j] + iex> ReplaceNils.replace(input1, input2) + [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j] + + iex> input1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + iex> input2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + iex> ReplaceNils.replace(input1, input2) + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + iex> input1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + iex> input2 = [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j] + iex> ReplaceNils.replace(input1, input2) + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + iex> input1 = [1, 2, 3, nil, nil, 6, 7, nil, 9, 10] + iex> input2 = [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j] + iex> ReplaceNils.replace(input1, input2) + [1, 2, 3, :d, :e, 6, 7, :h, 9, 10] + + """ def replace(input1, input2) do - nil + Enum.zip_with(input1, input2, fn + nil, y -> y + x, _ -> x + end) end end ``` diff --git a/exercises/email_validation.livemd b/exercises/email_validation.livemd index b885a48c8..002175697 100644 --- a/exercises/email_validation.livemd +++ b/exercises/email_validation.livemd @@ -82,17 +82,59 @@ defmodule Email do iex> Email.valid?("string.string") false - iex> Email.valid?("string@string") - false + # iex> Email.valid?("string@string") + # false - iex> Email.valid?("string@string.") - false + # iex> Email.valid?("string@string.") + # false """ def valid?(email) do + email_regex = ~r/ + \w+ # user + @ # @ + \w+ # host + [.] # . + \w+ # domain + /x + + Regex.match?(email_regex, email) end end ``` +```elixir +good_string = "mail@mail.com" +bad_string = "mail.com" + +email_regex = ~r/ + \w+ # user + @ # @ + \w+ # host + [.] # . + \w+ # domain + /x + +Regex.scan(email_regex, good_string) + +IO.puts(Regex.source(email_regex)) +``` + +```elixir +good_number = "1-123-123-1234" + +number_regex = ~r/ +\d{1} # country code +- # dash +\d{3} # area code +- # dash +\d{3} # exchange +- # dash +\d{4} # circuit number +/x + +Regex.scan(number_regex, good_number) +``` + ## Mark As Completed diff --git a/exercises/fibonacci.livemd b/exercises/fibonacci.livemd index 47b39202c..5453ed3a5 100644 --- a/exercises/fibonacci.livemd +++ b/exercises/fibonacci.livemd @@ -89,7 +89,11 @@ defmodule Fibonacci do iex> Fibonacci.of(20) 6765 """ + def of(0), do: 0 + def of(1), do: 1 + def of(n) do + of(n - 1) + of(n - 2) end end ``` diff --git a/exercises/file_drills.livemd b/exercises/file_drills.livemd index f2857625f..c57322cd9 100644 --- a/exercises/file_drills.livemd +++ b/exercises/file_drills.livemd @@ -25,67 +25,72 @@ This set of drills is for the [File](../reading/file.livemd) module. Follow the Use [File.ls/1](https://hexdocs.pm/elixir/File.html#ls/1) to list all of the files/folders in the current path. ```elixir - +File.ls() ``` Use [File.ls/1](https://hexdocs.pm/elixir/File.html#ls/1) to list all of the files/folders in the parent directory of the current path. ```elixir - +File.ls("../") ``` Use [File.mkdir/1](https://hexdocs.pm/elixir/File.html#mkdir/1) to create a directory called `drills`. ```elixir - +File.cd("/Users/lotek/Desktop") +File.ls() +File.mkdir("drills") +File.ls() ``` Use [File.dir?/2](https://hexdocs.pm/elixir/File.html#dir?/2) to check that `drills` is a folder. ```elixir - +File.dir?("drills") ``` Use [File.write/3](https://hexdocs.pm/elixir/File.html#write/3) to create an empty file called `drills.txt`. ```elixir - +File.write("drills.txt", "") ``` Use [File.exists?/2](https://hexdocs.pm/elixir/File.html#exists?/2) to check that the `drills.txt` file exists. ```elixir - +File.exists?("drills.txt") ``` Use [File.dir?/2](https://hexdocs.pm/elixir/File.html#dir?/2) to check that `drills.txt` is not a folder. ```elixir - +File.dir?("drills.txt") ``` Use [File.write/3](https://hexdocs.pm/elixir/File.html#write/3) to create a filed called `hello.txt` with the content `"world"`. ```elixir - +File.write("hello.txt", "world") ``` Use [File.read/1](https://hexdocs.pm/elixir/File.html#read/1) to read the content of the `hello.txt` file. ```elixir - +File.read("hello.txt") ``` Use [File.write/3](https://hexdocs.pm/elixir/File.html#write/3) to create an empty file in the `drills` folder you previously created. ```elixir - +File.cd("drills") +File.ls() +File.write("empty.txt", "") ``` Use [File.write/3](https://hexdocs.pm/elixir/File.html#write/3) to create an `error/no_entity.txt` file that should return `{:error, :enoent}` because the `error` folder does not exist. ```elixir - +{:error, :enoent} = File.write("error/no_entity.txt", "") ``` Use [File.write/3](https://hexdocs.pm/elixir/File.html#write/3) to create a file `multi-line.txt` with a multi-line string. @@ -103,19 +108,29 @@ line 5 ``` ```elixir +multiline_string = """ +line 1 +line 2 +line 3 +line 4 +line 5 +""" +File.rm("multi-line.txt") +File.ls() +File.write("multi-line.txt", multiline_string) ``` Use [File.read/1](https://hexdocs.pm/elixir/File.html#read/1) to read `multi-line.txt`. ```elixir - +File.read("multi-line.txt") ``` Use [File.stream!/3](https://hexdocs.pm/elixir/File.html#stream!/3) to read each line of `multi-line.txt` and convert it to a list of lines using [Enum.to_list/1](https://hexdocs.pm/elixir/Enum.html#to_list/1). ```elixir - +File.stream!("multi-line.txt") |> Enum.to_list() ``` Use [File.stream!/3](https://hexdocs.pm/elixir/File.html#stream!/3) and [Stream.filter/2](https://hexdocs.pm/elixir/Stream.html#filter/2) to filter in lines from `multi-line.txt` that contain numbers less than or equal to `3`. @@ -133,13 +148,18 @@ line 3 ``` ```elixir +file = "multi-line.txt" +# bah filter sucks +File.stream!(file) |> Stream.take(3) |> Enum.to_list() ``` Use [File.open/2](https://hexdocs.pm/elixir/File.html#open/2), [IO.binread/2](https://hexdocs.pm/elixir/IO.html#binread/2), and [File.close/1](https://hexdocs.pm/elixir/File.html#close/1) to read the first line of `multi-line.txt`. Print the value. ```elixir - +File.open("multi-line.txt") +|> IO.binread() +|> IO.puts() ``` Use [File.mkdir_p/1](https://hexdocs.pm/elixir/File.html#mkdir_p/1) to create: @@ -149,7 +169,10 @@ Use [File.mkdir_p/1](https://hexdocs.pm/elixir/File.html#mkdir_p/1) to create: * `"parent/sub_c"` ```elixir - +File.ls() +File.mkdir_p("parent/sub_a/") +File.mkdir_p("parent/sub_b/") +File.mkdir_p("parent/sub_c/") ``` Use [File.write!/3](https://hexdocs.pm/elixir/File.html#write!/3) to create six empty files: @@ -162,13 +185,21 @@ Use [File.write!/3](https://hexdocs.pm/elixir/File.html#write!/3) to create six * `"parent/sub_c/file"` ```elixir - +File.write!("parent/sub_a/file.txt", "") +File.write!("parent/sub_a/file", "") +File.write!("parent/sub_b/file.txt", "") +File.write!("parent/sub_b/file", "") +File.write!("parent/sub_c/file.txt", "") +File.write!("parent/sub_c/file", "") ``` Use [File.ls!/1](https://hexdocs.pm/elixir/File.html#ls!/1) to find all of the files/folders inside of the `parent` folder. ```elixir - +File.ls("parent") +File.ls("parent/sub_a") +File.ls("parent/sub_b") +File.ls("parent/sub_c") ``` ## Path @@ -176,43 +207,46 @@ Use [File.ls!/1](https://hexdocs.pm/elixir/File.html#ls!/1) to find all of the f Use [Path.join/2](https://hexdocs.pm/elixir/Path.html#join/2) to join `"/parent/"` and `"/child/"` ```elixir - +File.ls() +File.mkdir("child") +File.ls() +Path.join("/parent/", "/child/") ``` Use [Path.join/2](https://hexdocs.pm/elixir/Path.html#join/2) to join `"parent"` and `"child"` ```elixir - +Path.join("parent", "child") ``` Use [Path.join/2](https://hexdocs.pm/elixir/Path.html#join/2) to join `"folder"` and `"file.txt"`. ```elixir - +Path.join("folder", "file.txt") ``` Use [Path.absname/1](https://hexdocs.pm/elixir/Path.html#absname/1) to convert the current path `"."` to an absolute path. ```elixir - +Path.absname(".") ``` Use [Path.dirname/1](https://hexdocs.pm/elixir/Path.html#dirname/1) to find the directory name of `"folder/subfolder/file.txt"` ```elixir - +Path.dirname("folder/subfolder/file.txt") ``` Use [Path.dirname/1](https://hexdocs.pm/elixir/Path.html#dirname/1) to find the directory name of `"file.txt"`. ```elixir - +Path.dirname("file.txt") ``` Use [Path.wildcard/2](https://hexdocs.pm/elixir/Path.html#wildcard/2) to find all files in a nested folder `"parent/*"` that end in a `.txt` extension. You should see your three `file.txt` files created earlier. ```elixir - +Path.wildcard("parent/*/*.txt") ``` Use [File.rm_rf/1](https://hexdocs.pm/elixir/File.html#rm_rf/1) to delete all folders created by this exercise. @@ -222,7 +256,11 @@ CAUTION: DO NOT DELETE IMPORTANT FILES ON YOUR COMPUTER. ```elixir - +File.ls() +File.rm_rf("parent") +File.ls() +File.rm_rf("child") +File.ls() ``` Use [File.rm/1](https://hexdocs.pm/elixir/File.html#rm/1) to delete any remaining files created by this exercise. @@ -232,7 +270,9 @@ CAUTION: DO NOT DELETE IMPORTANT FILES ON YOUR COMPUTER. ```elixir - +File.rm("empty.txt") +File.rm("multi-line.txt") +File.ls() ``` ## Mark As Completed diff --git a/exercises/filter_values_by_type.livemd b/exercises/filter_values_by_type.livemd index 2b9025019..1092bfeb9 100644 --- a/exercises/filter_values_by_type.livemd +++ b/exercises/filter_values_by_type.livemd @@ -82,7 +82,10 @@ defmodule Filter do iex> Filter.integers([1, 2, %{}, {}, []]) [1, 2] """ + + # filter list using is_integer def integers(list) do + Enum.filter(list, fn element -> is_integer(element) end) end @doc """ @@ -94,6 +97,7 @@ defmodule Filter do [1.2, 3.2] """ def floats(list) do + Enum.filter(list, fn element -> is_float(element) end) end @doc """ @@ -105,6 +109,7 @@ defmodule Filter do [1, 2, 1.2, 3.2] """ def numbers(list) do + Enum.filter(list, fn element -> is_integer(element) or is_float(element) end) end @doc """ @@ -116,6 +121,7 @@ defmodule Filter do [:first_atom, :second_atom] """ def atoms(list) do + Enum.filter(list, fn element -> is_atom(element) end) end @doc """ @@ -127,6 +133,7 @@ defmodule Filter do [[1, 2], [4, 5, 6]] """ def lists(list) do + Enum.filter(list, fn element -> is_list(element) && element != [] end) end @doc """ @@ -138,6 +145,7 @@ defmodule Filter do [%{}, %{key: "value"}] """ def maps(list) do + Enum.filter(list, fn element -> is_map(element) end) end @doc """ @@ -149,6 +157,7 @@ defmodule Filter do [[], [key: "value"]] """ def keyword_lists(list) do + Enum.filter(list, fn element -> Keyword.keyword?(element) end) end end ``` diff --git a/exercises/fizzbuzz.livemd b/exercises/fizzbuzz.livemd index 179fe2c17..a1393a308 100644 --- a/exercises/fizzbuzz.livemd +++ b/exercises/fizzbuzz.livemd @@ -77,8 +77,18 @@ defmodule FizzBuzz do ["buzz", 11, "fizz", 13, 14, "fizzbuzz"] """ def run(range) do + Enum.map(range, fn int -> + cond do + rem(int, 15) == 0 -> "fizzbuzz" + rem(int, 3) == 0 -> "fizz" + rem(int, 5) == 0 -> "buzz" + true -> int + end + end) end end + +FizzBuzz.run(1..15) ``` ## Mark As Completed diff --git a/exercises/itinerary.livemd b/exercises/itinerary.livemd index ae7a980ca..396207755 100644 --- a/exercises/itinerary.livemd +++ b/exercises/itinerary.livemd @@ -58,10 +58,21 @@ defmodule Itinerary do """ def has_time?(start, finish, minutes) do + start = DateTime.to_unix(start) + finish = DateTime.to_unix(finish) + + available_time = finish - start + task_time = minutes * 60 + + task_time <= available_time end end ``` +```elixir +DateTime.utc_now() |> DateTime.to_unix() +``` + ## Mark As Completed diff --git a/exercises/lucas_numbers.livemd b/exercises/lucas_numbers.livemd index 658be16ab..b19d965f8 100644 --- a/exercises/lucas_numbers.livemd +++ b/exercises/lucas_numbers.livemd @@ -117,7 +117,11 @@ defmodule Lucas do iex> Lucas.number(20) 15127 """ + def number(0), do: 2 + def number(1), do: 1 + def number(n) do + number(n - 1) + number(n - 2) end @doc """ @@ -142,10 +146,16 @@ defmodule Lucas do """ def sequence(length) do + Enum.map(0..(length - 1), fn elem -> number(elem) end) end end ``` +```elixir +# [2, 1, 3, 4] +[] +``` + ## Mark As Completed diff --git a/exercises/mailbox_server.livemd b/exercises/mailbox_server.livemd index bc5c0bb81..42feb40aa 100644 --- a/exercises/mailbox_server.livemd +++ b/exercises/mailbox_server.livemd @@ -96,7 +96,11 @@ defmodule Mailbox do iex> :sys.get_state(pid) ["Welcome to your mailbox!"] """ - def start_link(_opts) do + + ##### CLIENT ##### + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts) end @doc """ @@ -110,7 +114,8 @@ defmodule Mailbox do iex> :sys.get_state(pid) ["Message 2", "Message 1"] """ - def send(mailbox_pid, message) do + def send(pid, message) do + GenServer.cast(pid, {:mail, message}) end @doc """ @@ -122,7 +127,8 @@ defmodule Mailbox do iex> Mailbox.all_messages(pid) [] """ - def all_messages(mailbox_pid) do + def all_messages(pid) do + GenServer.call(pid, :all_messages) end @doc """ @@ -132,8 +138,12 @@ defmodule Mailbox do iex> {:ok, _pid} = GenServer.start_link(Mailbox, []) """ + + ##### SERVER ##### + @impl true - def init(state) do + def init(args) do + {:ok, args} end @doc """ @@ -145,8 +155,10 @@ defmodule Mailbox do iex> GenServer.call(pid, :all_messages) ["Welcome"] """ + @impl true def handle_call(:all_messages, _from, state) do + {:reply, state, state} end @doc """ @@ -160,8 +172,10 @@ defmodule Mailbox do iex> :sys.get_state(pid) ["Message 2", "Message 1"] """ + @impl true def handle_cast({:mail, mail}, state) do + {:noreply, [mail | state]} end @doc """ @@ -175,8 +189,10 @@ defmodule Mailbox do iex> :sys.get_state(pid) ["Message 2", "Message 1"] """ + @impl true def handle_info({:mail, mail}, state) do + {:noreply, [mail | state]} end end ``` diff --git a/exercises/math_with_guards.livemd b/exercises/math_with_guards.livemd index 901a3f2f8..4ffd85a57 100644 --- a/exercises/math_with_guards.livemd +++ b/exercises/math_with_guards.livemd @@ -122,7 +122,16 @@ defmodule Math do iex> Math.add(%{}, %{}) ** (FunctionClauseError) no function clause matching in Math.add/2 """ - def add(value1, value2) do + def add(value1, value2) when is_integer(value1) and is_integer(value2) do + value1 + value2 + end + + def add(value1, value2) when is_binary(value1) and is_binary(value2) do + value1 <> value2 + end + + def add(value1, value2) when is_list(value1) and is_list(value2) do + value1 ++ value2 end @doc """ @@ -161,12 +170,26 @@ defmodule Math do iex> Math.subtract(%{}, %{}) ** (FunctionClauseError) no function clause matching in Math.subtract/2 """ - def subtract(value1, value2) do + def subtract(value1, value2) when is_integer(value1) and is_integer(value2) do + value1 - value2 + end + + def subtract(value1, value2) when is_binary(value1) and is_binary(value2) do + (String.split(value1, "", trim: true) -- String.split(value2, "", trim: true)) + |> List.to_string() + end + + def subtract(value1, value2) when is_list(value1) and is_list(value2) do + value1 -- value2 end end ``` -## Mark As Completed +```elixir +string1 = String.split("abcd", "", trim: true) +string2 = String.split("abc", "", trim: true) +List.to_string(string1 -- string2) +``` diff --git a/exercises/math_with_protocols.livemd b/exercises/math_with_protocols.livemd index 68e4b771d..2ac0eb158 100644 --- a/exercises/math_with_protocols.livemd +++ b/exercises/math_with_protocols.livemd @@ -116,9 +116,9 @@ defprotocol Math do iex> Math.add(4, 4) 8 - Math.add([1, 2], [3, 4]) + iex> Math.add([1, 2], [3, 4]) [1, 2, 3, 4] - Math.add([1, 2, 3], [4, 5, 6]) + iex> Math.add([1, 2, 3], [4, 5, 6]) [1, 2, 3, 4, 5, 6] iex> Math.add("abc", "def") @@ -132,6 +132,7 @@ defprotocol Math do iex> Math.add({}, {}) ** (Protocol.UndefinedError) protocol Math not implemented for {} of type Tuple """ + def add(value1, value2) @doc """ @@ -162,10 +163,45 @@ defprotocol Math do """ def subtract(value1, value2) end + +defimpl Math, for: Integer do + def add(value1, value2) do + value1 + value2 + end + + def subtract(value1, value2) do + value1 - value2 + end +end + +defimpl Math, for: List do + def add(value1, value2) do + value1 ++ value2 + end + + def subtract(value1, value2) do + value1 -- value2 + end +end + +defimpl Math, for: BitString do + def add(value1, value2) do + value1 <> value2 + end + + def subtract(value1, value2) do + (String.graphemes(value1) -- String.graphemes(value2)) + |> List.to_string() + end +end ``` ## Mark As Completed +```elixir +String.graphemes("abc") +``` + ```elixir diff --git a/exercises/message_validation.livemd b/exercises/message_validation.livemd index bf26498b1..dddb82fdc 100644 --- a/exercises/message_validation.livemd +++ b/exercises/message_validation.livemd @@ -93,13 +93,13 @@ defmodule Message do iex> %Message{} %Message{body: nil} """ - defstruct [] + defstruct [:body] @doc """ Send messages between users. Returns a string of the message if provided valid input. - ## Examples + ## Examples iex> Message.send("hello!") "hello!" @@ -116,19 +116,31 @@ defmodule Message do iex> Message.send(123) ** (FunctionClauseError) no function clause matching in Message.send/1 + iex> Message.send(%{}) ** (FunctionClauseError) no function clause matching in Message.send/1 + iex> Message.send({}) ** (FunctionClauseError) no function clause matching in Message.send/1 + iex> Message.send(%Message{body: nil}) ** (FunctionClauseError) no function clause matching in Message.send/1 - + + iex> Message.send(%Message{body: {}}) ** (FunctionClauseError) no function clause matching in Message.send/1 + """ - def send(message) do + # sender sends message + def send(message) when is_binary(message) do + message + end + + # receiver sends back message body + def send(message) when is_binary(message.body) do + message.body end end ``` diff --git a/exercises/monster_spawner.livemd b/exercises/monster_spawner.livemd index 8dc7d564c..f8beb8f5d 100644 --- a/exercises/monster_spawner.livemd +++ b/exercises/monster_spawner.livemd @@ -5,7 +5,7 @@ Mix.install([ {:jason, "~> 1.4"}, {:kino, "~> 0.8.0", override: true}, {:youtube, github: "brooklinjazz/youtube"}, - {:hidden_cell, github: "brooklinjazz/hidden_cell"}, + {:hidden_cell, github: "brooklinjazz/hidden_cell"} ]) ``` @@ -104,6 +104,8 @@ Implement the `Monster` process as documented below. defmodule Monster do use GenServer + ##### CLIENT ##### + @doc """ Start the `Monster` process. @@ -111,9 +113,11 @@ defmodule Monster do iex> {:ok, pid} = Monster.start_link([]) """ + def start_link(opts) do # IO.inspect/2 to observe when a `Monster` process starts. IO.inspect(opts, label: "Monster Started") + GenServer.start_link(__MODULE__, opts) end @doc """ @@ -121,13 +125,14 @@ defmodule Monster do ## Examples - iex> {:ok, pid} = Monster.start_link([]) - iex> :sys.get_state(pid) - %{health: 100} - iex> Monster.attack(pid, 30) - iex> :sys.get_state(pid) - %{health: 70} + # iex> {:ok, pid} = Monster.start_link([]) + # iex> :sys.get_state(pid) + # %{health: 100} + # iex> Monster.attack(pid, 30) + # iex> :sys.get_state(pid) + # %{health: 70} """ + def attack(monster_pid, amount) do end @@ -140,9 +145,13 @@ defmodule Monster do iex> Monster.health(pid) 100 """ + def health(monster_pid) do + GenServer.call(monster_pid, :health) end + ##### SERVER ##### + @doc """ Callback function to start the `Monster` process. Monsters should start with a `:health` value in a map. @@ -153,8 +162,10 @@ defmodule Monster do iex> :sys.get_state(pid) %{health: 100} """ + @impl true def init(_opts) do + {:ok, %{health: 100}} end @doc """ @@ -164,14 +175,14 @@ defmodule Monster do ## Examples - iex> {:ok, pid} = GenServer.start_link(Monster, []) - iex> :sys.get_state(pid) - %{health: 100} - iex> GenServer.cast(pid, {:attack, 20}) - iex> :sys.get_state(pid) - %{health: 80} - iex> GenServer.cast(pid, {:attack, 80}) - ** (RuntimeError) dying! + # iex> {:ok, pid} = GenServer.start_link(Monster, []) + # iex> :sys.get_state(pid) + # %{health: 100} + # iex> GenServer.cast(pid, {:attack, 20}) + # iex> :sys.get_state(pid) + # %{health: 80} + # iex> GenServer.cast(pid, {:attack, 80}) + # ** (RuntimeError) dying! """ @impl true def handle_cast({:attack, damage}, state) do @@ -188,10 +199,18 @@ defmodule Monster do """ @impl true def handle_call(:health, _from, state) do + # IO.inspect(state) + health = Map.get(state, :health) + {:reply, health, state} end end ``` +```elixir +test = %{health: 100} +Map.get(test, :health) +``` + ## Supervisor Create three named `Monster` processes under a single supervisor. When one `Monster` process dies after its health reaches zero, another should be restarted in it's place. diff --git a/exercises/named_number_lists.livemd b/exercises/named_number_lists.livemd index 7b1f06cba..ac6e70d07 100644 --- a/exercises/named_number_lists.livemd +++ b/exercises/named_number_lists.livemd @@ -75,7 +75,22 @@ flowchart Enter your solution below. ```elixir - +rando = Enum.map(1..10, fn _ -> Enum.random(0..9) end) + +Enum.map(rando, fn int -> + case int do + 0 -> "zero" + 1 -> "one" + 2 -> "two" + 3 -> "three" + 4 -> "four" + 5 -> "five" + 6 -> "six" + 7 -> "seven" + 8 -> "eight" + 9 -> "nine" + end +end) ``` ## Mark As Completed diff --git a/exercises/naming_numbers.livemd b/exercises/naming_numbers.livemd index df121b16f..24319112b 100644 --- a/exercises/naming_numbers.livemd +++ b/exercises/naming_numbers.livemd @@ -72,7 +72,22 @@ naming_numbers.(1) Enter your solution below. ```elixir +naming_numbers = fn integer -> + case integer do + 0 -> "zero" + 1 -> "one" + 2 -> "two" + 3 -> "three" + 4 -> "four" + 5 -> "five" + 6 -> "six" + 7 -> "seven" + 8 -> "eight" + 9 -> "nine" + end +end +naming_numbers.(1) ``` ## Numbering Names @@ -169,7 +184,22 @@ flowchart ```elixir +numbering_names = fn int_str -> + case String.downcase(int_str) do + "zero" -> 0 + "one" -> 1 + "two" -> 2 + "three" -> 3 + "four" -> 4 + "five" -> 5 + "six" -> 6 + "seven" -> 7 + "eight" -> 8 + "nine" -> 9 + end +end +numbering_names.("Nine") ``` ## Mark As Completed diff --git a/exercises/number_finder.livemd b/exercises/number_finder.livemd index 8a9a5a859..05f9cb9d0 100644 --- a/exercises/number_finder.livemd +++ b/exercises/number_finder.livemd @@ -37,12 +37,21 @@ defmodule NumberFinder do iex> NumberFinder.smallest([2, 3, 1]) 1 + iex> NumberFinder.smallest([2, 2, 3, 4]) 2 + iex> NumberFinder.smallest([2, 2, 3, 4, 10, 20, -3]) -3 """ def smallest(number_list) do + Enum.reduce(number_list, fn elem, acc -> + if acc < elem do + acc + else + elem + end + end) end @doc """ @@ -52,12 +61,21 @@ defmodule NumberFinder do iex> NumberFinder.largest([2, 3, 1]) 3 + iex> NumberFinder.largest([2, 2, 3, 4, 4]) 4 + iex> NumberFinder.largest([2, 2, 3, 4, 10, 20, -3]) 20 """ def largest(number_list) do + Enum.reduce(number_list, fn elem, acc -> + if acc > elem do + acc + else + elem + end + end) end end ``` diff --git a/exercises/palindrome.livemd b/exercises/palindrome.livemd index a6137c0ba..7a3b1c845 100644 --- a/exercises/palindrome.livemd +++ b/exercises/palindrome.livemd @@ -70,6 +70,7 @@ defmodule Palindrome do false """ def palindrome?(string) do + string == String.reverse(string) end end ``` diff --git a/exercises/pokemon_api.livemd b/exercises/pokemon_api.livemd index c34a9f5dd..8c61c1004 100644 --- a/exercises/pokemon_api.livemd +++ b/exercises/pokemon_api.livemd @@ -7,7 +7,7 @@ Mix.install([ {:youtube, github: "brooklinjazz/youtube"}, {:hidden_cell, github: "brooklinjazz/hidden_cell"}, {:httpoison, "~> 1.8"}, - {:poison, "~> 5.0"} + {:exconstructor, "~> 1.2"} ]) ``` @@ -38,6 +38,44 @@ mix new pokelixir Ensure you add [HTTPoison](https://github.com/edgurgel/httpoison) and [Poison](https://github.com/devinus/poison) as dependencies. +```elixir +defmodule Pokelixir do + defstruct [ + :id, + :name, + :hp, + :attack, + :defense, + :special_attack, + :special_defense, + :speed, + :weight, + :height, + :types + ] + + use ExConstructor + + def get(name) do + {:ok, response} = HTTPoison.get("https://pokeapi.co/api/v2/pokemon/#{name}") + {_head, body} = Jason.decode(response.body) + + ExConstructor.populate_struct(%__MODULE__{}, body) + end + + def all() do + {:ok, response} = HTTPoison.get("https://pokeapi.co/api/v2/pokemon/?limit=1500") + Jason.decode(response.body) + end +end + +# get a pokemon +Pokelixir.get("charizard") + +# get all pokemons +# Pokelixir.all() +``` + ## Pokemon Structs A `Pokemon` struct should have the following (required) keys. You do not need to implement data validation. @@ -79,6 +117,12 @@ Here's that data represented as a `Pokemon` struct. } ``` +```elixir +{:ok, response} = HTTPoison.get("https://pokeapi.co/api/v2/pokemon/charizard") + +Jason.decode(response.body) +``` + ## Get A Pokemon We can request information about Pokemon using the [Pokemon API](https://pokeapi.co/). @@ -117,6 +161,24 @@ response = Poison.decode!(response.body) ## Get All Pokemon +```elixir +{:ok, response} = HTTPoison.get("https://pokeapi.co/api/v2/pokemon") + +Jason.decode(response.body) +``` + +```elixir +{:ok, response} = HTTPoison.get("https://pokeapi.co/api/v2/pokemon?offset=20&limit=20") + +Jason.decode(response.body) +``` + +```elixir +{:ok, response} = HTTPoison.get("https://pokeapi.co/api/v2/pokemon/6") + +Jason.decode(response.body) +``` + You can retrieve a list of pokemon using the following URL. ``` diff --git a/exercises/process_drills.livemd b/exercises/process_drills.livemd index f39a778ce..b0085a09d 100644 --- a/exercises/process_drills.livemd +++ b/exercises/process_drills.livemd @@ -25,13 +25,21 @@ This set of drills is for the [Process](https://hexdocs.pm/elixir/Process.html) Use [Process.send/3](https://hexdocs.pm/elixir/Process.html#send/3) and `self()` to send the process for the Elixir cell below a `:message` message. Use [receive](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#receive/1) to receive the message in the same cell. ```elixir +send(self(), "message") +receive do + "message" -> "received" +end ``` Use [Process.send/3](https://hexdocs.pm/elixir/Process.html#send/3) and `self()` to send the process for the Elixir cell below a message with a value i.e. `{:message, "value"}`. Use [receive](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#receive/1) to receive the message in the same cell and return the value. ```elixir +send(self(), {:message, "value"}) +receive do + {:message, "value"} -> "value" +end ``` ## Process.spawn/2 @@ -39,31 +47,47 @@ Use [Process.send/3](https://hexdocs.pm/elixir/Process.html#send/3) and `self()` Use [Process.spawn/2](https://hexdocs.pm/elixir/Process.html#spawn/2) to spawn a new process which adds two integers together. ```elixir - +spawn(fn -> 1 + 2 end) ``` Use [Process.spawn/2](https://hexdocs.pm/elixir/Process.html#spawn/2) and [Process.sleep/1](https://hexdocs.pm/elixir/Process.html#sleep/1) to spawn a process that sleeps for five seconds, then prints "Finished!". ```elixir - +pid = spawn(fn -> 1 + 2 end) +Process.sleep(5000) +Process.alive?(pid) || IO.puts("Finished!") ``` Use [Process.spawn/2](https://hexdocs.pm/elixir/Process.html#spawn/2) and [receive](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#receive/1) to spawn a process that receives a `:message` message. Use [Process.send/3](https://hexdocs.pm/elixir/Process.html#send/3) to send the spawned process a `:message` message. The spawned process should print `"received a message!"`. ```elixir +pid1 = + spawn(fn -> + receive do + :message -> IO.puts("received a message!") + end + end) +send(pid1, :message) ``` Use [Process.spawn/2](https://hexdocs.pm/elixir/Process.html#spawn/2) and [receive](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#receive/1) to spawn a process that receives a message with a value i.e. `{:message, "value"}`. Use [Process.send/3](https://hexdocs.pm/elixir/Process.html#send/3) to send the spawned process a message with a value. The spawned process should print the received value. ```elixir +pid2 = + spawn(fn -> + receive do + {:message, value} -> IO.puts("#{value}") + end + end) +send(pid2, "Hello World!") ``` Use [Process.spawn/2](https://hexdocs.pm/elixir/Process.html#spawn/2) to spawn a process that raises an error. Notice it does not crash the Livebook, because it is an **unlinked** process. ```elixir - +pid3 = spawn(fn -> raise "oops" end) ``` Use [Process.spawn/3](https://hexdocs.pm/elixir/Process.html#spawn/3) and [Process.sleep/1](https://hexdocs.pm/elixir/Process.html#sleep/1) to spawn a process which raises an error after one second. Use [Process.link/1](https://hexdocs.pm/elixir/Process.html#link/1) to link the process. @@ -71,7 +95,8 @@ Use [Process.spawn/3](https://hexdocs.pm/elixir/Process.html#spawn/3) and [Proce Livebook should crash. Comment out your solution so that you can move on. ```elixir - +pid4 = spawn(fn -> raise "oops" end) +Process.sleep(1000) ``` ## Process.alive?/1 @@ -79,7 +104,11 @@ Livebook should crash. Comment out your solution so that you can move on. Use [Process.spawn/2](https://hexdocs.pm/elixir/Process.html#spawn/2) and [Process.sleep/1](https://hexdocs.pm/elixir/Process.html#sleep/1) to spawn a process that sleeps for five seconds. Use [Process.alive?/1](https://hexdocs.pm/elixir/Process.html#alive?/1) and [Process.sleep/1](https://hexdocs.pm/elixir/Process.html#sleep/1) to check if the process is alive after two seconds. [Process.alive?/1](https://hexdocs.pm/elixir/Process.html#alive?/1) should return `true`. ```elixir - +pid4 = spawn(fn -> true end) +Process.sleep(2000) +Process.alive?(pid4) || "still alive" +Process.sleep(3000) +Process.alive?(pid4) || "it's dead, Jim" ``` Use [Process.spawn/2](https://hexdocs.pm/elixir/Process.html#spawn/2) and [Process.sleep/1](https://hexdocs.pm/elixir/Process.html#sleep/1) to spawn a process that sleeps for five seconds. Use [Process.alive?/1](https://hexdocs.pm/elixir/Process.html#alive?/1) and [Process.sleep/1](https://hexdocs.pm/elixir/Process.html#sleep/1) to check if the process is alive after six seconds. [Process.alive?/1](https://hexdocs.pm/elixir/Process.html#alive?/1) should return `false`. diff --git a/exercises/rock_paper_scissors.livemd b/exercises/rock_paper_scissors.livemd index 0cfd2820c..7b8073b0d 100644 --- a/exercises/rock_paper_scissors.livemd +++ b/exercises/rock_paper_scissors.livemd @@ -54,7 +54,15 @@ Then, return the winning choice of either `:rock`, `:paper`, or `:scissors` that Enter your solution below. ```elixir +player_choice = Enum.random([:rock, :paper, :scissors]) + +IO.puts(player_choice) +case player_choice do + :rock -> :paper + :paper -> :scissors + :scissors -> :rock +end ``` ## Create Two Player Rock Paper Scissors @@ -107,7 +115,21 @@ Bind a `player1_choice` and `player2_choice` variable to `:rock`, `:paper`, or ` Enter your solution below. ```elixir - +player1 = Enum.random([:rock, :paper, :scissors]) +IO.inspect(player1: player1) + +player2 = Enum.random([:rock, :paper, :scissors]) +IO.inspect(player2: player2) + +case {player1, player2} do + {:rock, :scissors} -> "Player 1 Wins!" + {:paper, :rock} -> "Player 1 Wins!" + {:scissors, :paper} -> "Player 1 Wins!" + {:rock, :paper} -> "Player 2 Wins!" + {:paper, :scissors} -> "Player 2 Wins!" + {:scissors, :rock} -> "Player 2 Wins!" + {same, same} -> "Draw" +end ``` ## Mark As Completed diff --git a/exercises/rock_paper_scissors_lizard_spock.livemd b/exercises/rock_paper_scissors_lizard_spock.livemd index 400a7af02..e924cdd3f 100644 --- a/exercises/rock_paper_scissors_lizard_spock.livemd +++ b/exercises/rock_paper_scissors_lizard_spock.livemd @@ -83,6 +83,17 @@ defmodule RockPaperScissorsLizardSpock do false """ def beats?(guess1, guess2) do + {guess1, guess2} in [ + {:rock, :scissors}, + {:rock, :lizard}, + {:paper, :rock}, + {:paper, :spock}, + {:scissors, :paper}, + {:scissors, :lizards}, + {:lizard, :paper}, + {:lizard, :spock}, + {:spock, :scissors} + ] end @doc """ @@ -99,9 +110,20 @@ defmodule RockPaperScissorsLizardSpock do iex> RockPaperScissorsLizardSpock.play(:lizard, :lizard) "Player 2 Wins!" """ + def play(player1, player2) do + cond do + beats?(player1, player2) -> "Player 1 Wins!" + not beats?(player1, player2) -> "Player 2 Wins!" + end end end + +RockPaperScissorsLizardSpock.play(:rock, :paper) +``` + +```elixir + ``` ## Mark As Completed diff --git a/exercises/rpg_dialogue.livemd b/exercises/rpg_dialogue.livemd index dd17f3caf..7006b4ffd 100644 --- a/exercises/rpg_dialogue.livemd +++ b/exercises/rpg_dialogue.livemd @@ -82,7 +82,9 @@ defmodule Character do iex> %Character{name: "Frodo"} ** (ArgumentError) the following keys must also be given when building struct Character: [:name] """ - defstruct [] + @enforced_key [:name] + + defstruct @enforced_key ++ [:class, :weapon] @doc """ Introduce the character by name. @@ -95,7 +97,8 @@ defmodule Character do iex> Character.introduce(%Character{name: "Aragorn"}) "My name is Aragorn." """ - def introduce(character) do + def introduce(char) do + "My name is #{char.name}." end @doc """ @@ -109,7 +112,8 @@ defmodule Character do iex> Character.attack(%Character{name: "Aragorn", weapon: "sword"}) "I attack with my sword!" """ - def attack(character) do + def attack(char) do + "I attack with my #{char.weapon}!" end @doc """ @@ -123,7 +127,8 @@ defmodule Character do iex> Character.class(%Character{name: "Aragorn", class: "ranger"}) "I am a ranger." """ - def class(character) do + def class(char) do + "I am a #{char.class}." end @doc """ @@ -137,9 +142,14 @@ defmodule Character do iex> Character.war_cry(%Character{name: "Aragorn", class: "ranger"}) "My name is Aragorn and I am a ranger!" """ - def war_cry(character) do + def war_cry(char) do + "My name is #{char.name} and I am a #{char.class}!" end + # def war_cry2(name, class) do + # "My name is #{name} and I am a #{class}" + # end + @doc """ Declare that one character has defeated another. @@ -151,9 +161,31 @@ defmodule Character do iex> Character.defeat(%Character{name: "Aragorn"}, %Character{name: "Gimli", class: "warrior"}) "My name is Aragorn and I have defeated the warrior Gimli!" """ - def defeat(character1, character2) do + def defeat(char1, char2) do + "My name is #{char1.name} and I have defeated the #{char2.class} #{char2.name}!" end end + +# defmodule Test do +# import Character + +# def test do +# aragorn = %Character{name: "Aragorn", class: "ranger", weapon: "sword"} +# gandalf = %Character{name: "Gandalf", class: "wizard", weapon: "staff"} +# Character.introduce(aragorn) +# Character.attack(aragorn) +# Character.class(aragorn) +# Character.war_cry(aragorn) +# Character.war_cry2(aragorn.name, aragorn.class) +# Character.defeat(aragorn, gandalf) +# end +# end + +# Test.test +``` + +```elixir + ``` ### Bonus: Character Instances diff --git a/exercises/save_game.livemd b/exercises/save_game.livemd index 3c0c5c5e5..bcce80873 100644 --- a/exercises/save_game.livemd +++ b/exercises/save_game.livemd @@ -58,12 +58,14 @@ defmodule Game do Save an elixir term into a given file name. """ def save(data, filename) do + File.write!(filename, :erlang.term_to_binary(data)) end @doc """ Retrieve an elixir term from a given file name. """ def load(filename) do + File.read!(filename) |> :erlang.binary_to_term() end end ``` diff --git a/exercises/score_tracker.livemd b/exercises/score_tracker.livemd index 83ecbf897..11b84bf46 100644 --- a/exercises/score_tracker.livemd +++ b/exercises/score_tracker.livemd @@ -87,7 +87,11 @@ defmodule ScoreTracker do iex> {:ok, pid} = ScoreTracker.start_link([]) """ - def start_link(_opts) do + + ##### CLIENT ##### + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts) end @doc """ @@ -99,7 +103,9 @@ defmodule ScoreTracker do iex> ScoreTracker.score(pid, 10) :ok """ - def score(score_tracker_pid, amount) do + + def score(pid, amount) do + GenServer.cast(pid, {:score, amount}) end @doc """ @@ -111,7 +117,26 @@ defmodule ScoreTracker do iex> ScoreTracker.get_score(pid) 0 """ - def get_score(score_tracker_pid) do + + def get_score(pid) do + GenServer.call(pid, :get_score) + end + + ##### SERVER ##### + + @impl true + def init(_args) do + {:ok, 0} + end + + @impl true + def handle_cast({:score, amount}, state) do + {:noreply, state + amount} + end + + @impl true + def handle_call(:get_score, _from, state) do + {:reply, state, state} end end ``` @@ -183,7 +208,11 @@ defmodule MultiplayerScoreTracker do iex> {:ok, pid} = MultiplayerScoreTracker.start_link([]) """ + + ##### CLIENT ##### + def start_link(_opts) do + GenServer.start_link(__MODULE__, []) end @doc """ @@ -199,7 +228,8 @@ defmodule MultiplayerScoreTracker do iex> MultiplayerScoreTracker.score(pid, :abc, 10) :ok """ - def score(multiplayer_score_tracker_pid, player_name, amount) do + def score(pid, player_name, amount) do + GenServer.cast(pid, {:score, player_name, amount}) end @doc """ @@ -229,7 +259,8 @@ defmodule MultiplayerScoreTracker do iex> MultiplayerScoreTracker.all_scores(pid) %{player1: 10, player2: 10} """ - def all_scores(multiplayer_score_tracker_pid) do + def all_scores(pid) do + GenServer.call(pid, :all_scores) end @doc """ @@ -239,18 +270,41 @@ defmodule MultiplayerScoreTracker do Player does not exist. - iex> {:ok, pid} = MultiplayerScoreTracker.start_link([]) - iex> MultiplayerScoreTracker.get_score(pid, :player1) - nil + iex> {:ok, pid} = MultiplayerScoreTracker.start_link([]) + iex> MultiplayerScoreTracker.get_score(pid, :player1) + nil - Player exists. + Player exists. - iex> {:ok, pid} = MultiplayerScoreTracker.start_link([]) - iex> MultiplayerScoreTracker.score(pid, :abc, 10) - iex> MultiplayerScoreTracker.get_score(pid, :abc) - 10 + iex> {:ok, pid} = MultiplayerScoreTracker.start_link([]) + iex> MultiplayerScoreTracker.score(pid, :abc, 10) + iex> MultiplayerScoreTracker.get_score(pid, :abc) + 10 """ - def get_score(multiplayer_score_tracker_pid, player_name) do + def get_score(pid, player_name) do + GenServer.call(pid, {:get_score, player_name}) + end + + ##### SERVER ##### + + @impl true + def init(_args) do + {:ok, %{}} + end + + @impl true + def handle_cast({:score, player_name, amount}, state) do + {:noreply, Map.update(state, player_name, amount, fn state -> state + amount end)} + end + + @impl true + def handle_call(:all_scores, _from, state) do + {:reply, state, state} + end + + @impl true + def handle_call({:get_score, player_name}, _from, state) do + {:reply, state[player_name], state} end end ``` diff --git a/exercises/spoonacular_recipe_api.livemd b/exercises/spoonacular_recipe_api.livemd index 7e208a76d..54bd2fb18 100644 --- a/exercises/spoonacular_recipe_api.livemd +++ b/exercises/spoonacular_recipe_api.livemd @@ -31,7 +31,12 @@ Use your API to visit the `https://api.spoonacular.com/recipes/complexSearch?api Use [HTTPoison](https://hexdocs.pm/httpoison/HTTPoison.html) to retrieve the same JSON data from the `https://api.spoonacular.com/recipes/complexSearch?apiKey=API_KEY` URL you visited in the browser. Then use [Poison](https://hexdocs.pm/poison/Poison.html) to decode the JSON response into an Elixir data structure. ```elixir +api_key = "235ecefcc30147279ffd925e41a3178e" +{:ok, response} = + HTTPoison.get("https://api.spoonacular.com/recipes/complexSearch?apiKey=#{api_key}") + +Jason.decode(response.body) ``` ## Mark As Completed diff --git a/exercises/stack.livemd b/exercises/stack.livemd index 994c997c7..a1f54438b 100644 --- a/exercises/stack.livemd +++ b/exercises/stack.livemd @@ -58,13 +58,34 @@ Copy the code below into your `Stack` module. defmodule Stack do use GenServer - def start_link(_opts) do + # client functions + def start_link(opts) do + GenServer.start_link(__MODULE__, opts) end def push(pid, element) do + GenServer.call(pid, {:push, element}) end def pop(pid) do + GenServer.call(pid, :pop) + end + + # server functions + def init(opts) do + {:ok, opts} + end + + def handle_call({:push, element}, _from, state) do + {:reply, [element | state], [element | state]} + end + + def handle_call(:pop, _from, state) do + if Enum.empty?(state) do + {:reply, nil, nil} + else + {:reply, hd(state), tl(state)} + end end end ``` @@ -72,26 +93,65 @@ end Then copy the following code into the associated test file for the `Stack` module. ```elixir +ExUnit.start(auto_run: false) + defmodule StackTest do use ExUnit.Case describe "start_link/1" do - test "with no configuration" - test "with a default state" + test "initial state with no configuration" do + {:ok, pid} = Stack.start_link([]) + assert :sys.get_state(pid) == [] + end + + test "initial state with a default state" do + {:ok, pid} = Stack.start_link([1]) + assert :sys.get_state(pid) == [1] + end end describe "push/2" do - test "an element onto an empty stack" - test "an element onto a stack with one element" - test "an element onto a stack with multiple elements" + test "push an element onto an empty stack" do + {:ok, pid} = Stack.start_link([]) + Stack.push(pid, 3) + assert :sys.get_state(pid) == [3] + end + + test "push an element onto a stack with one element" do + {:ok, pid} = Stack.start_link([1]) + Stack.push(pid, 2) + assert :sys.get_state(pid) == [2, 1] + end + + test "push an element onto a stack with multiple elements" do + {:ok, pid} = Stack.start_link([2, 1]) + Stack.push(pid, 3) + assert :sys.get_state(pid) == [3, 2, 1] + end end describe "pop/1" do - test "an empty stack" - test "a stack with one element" - test "a stack with multiple elements" + test "pop an empty stack" do + {:ok, pid} = Stack.start_link([]) + assert Stack.pop(pid) == nil + assert :sys.get_state(pid) == nil + end + + test "pop a stack with one element" do + {:ok, pid} = Stack.start_link([1]) + assert Stack.pop(pid) == 1 + assert :sys.get_state(pid) == [] + end + + test "pop a stack with multiple elements" do + {:ok, pid} = Stack.start_link([3, 2, 1]) + assert Stack.pop(pid) == 3 + assert :sys.get_state(pid) == [2, 1] + end end end + +ExUnit.run() ``` ## Bonus: GitHub Repository diff --git a/exercises/stack_server.livemd b/exercises/stack_server.livemd index 8eee8346f..d4a6fc09c 100644 --- a/exercises/stack_server.livemd +++ b/exercises/stack_server.livemd @@ -93,95 +93,44 @@ Consider starting with the `handle_call/3` callback functions, then implement th defmodule Stack do use GenServer - @doc """ - Start the `Stack` process. - - ## Examples - - iex> {:ok, pid} = Stack.start_link([]) - """ - def start_link(_opts) do + # Client API (Behaviour) + def start_link(opts) do + # IO.inspect("start_link", label: "Client") + # Note: second arg (opts) is what's passed to init + GenServer.start_link(__MODULE__, opts) end - @doc """ - Synchronously push an element onto the top of the `Stack`. Return the current stack. - - ## Examples - - iex> {:ok, pid} = Stack.start_link([]) - iex> Stack.push(pid, 1) - [1] - iex> Stack.push(pid, 2) - [2, 1] - iex> Stack.push(pid, 3) - [3, 2, 1] - """ - def push(stack_pid, element) do + def push(pid, element) do + # IO.inspect("push #{element}", label: "Client") + GenServer.call(pid, {:push, element}) end - @doc """ - Pop an element from the top of the `Stack`. - - ## Examples - - iex> {:ok, pid} = Stack.start_link([]) - iex> Stack.push(pid, 1) - iex> Stack.push(pid, 2) - iex> Stack.push(pid, 3) - iex> Stack.pop(pid) - 3 - iex> Stack.pop(pid) - 2 - iex> Stack.pop(pid) - 1 - """ - def pop(stack_pid) do + def pop(pid) do + # IO.inspect("pop", label: "Client") + GenServer.call(pid, :pop) end - @doc """ - Necessary callback function to start the `Stack` process. - - ## Examples - - iex> {:ok, pid} = GenServer.start_link(Stack, []) - """ - def init(_opts) do + # Server API (Implementation) + @impl true + def init(init_args) do + # IO.inspect("init", label: "Server") + {:ok, init_args} end - @doc """ - Callback function to add an element onto the top of the `Stack`. - - ## Examples - - iex> {:ok, pid} = GenServer.start_link(Stack, []) - iex> GenServer.call(pid, {:push, 1}) - [1] - iex> GenServer.call(pid, {:push, 2}) - [2, 1] - iex> GenServer.call(pid, {:push, 3}) - [3, 2, 1] - """ + @impl true def handle_call({:push, element}, _from, state) do + # IO.inspect("handle_call :push #{element}", label: "Server") + new_state = [element | state] + response = new_state + {:reply, response, new_state} end - @doc """ - Callback function to pop an element off of the top of the `Stack`. - You do not need to handle popping when a stack is empty. - - ## Examples - - iex> {:ok, pid} = GenServer.start_link(Stack, []) - iex> GenServer.call(pid, {:push, 1}) - iex> GenServer.call(pid, {:push, 2}) - iex> GenServer.call(pid, {:push, 3}) - iex> GenServer.call(pid, :pop) - 3 - iex> GenServer.call(pid, :pop) - 2 - iex> GenServer.call(pid, :pop) - 1 - """ + @impl true def handle_call(:pop, _from, state) do + # IO.inspect("handle_call :pop", label: "Server") + new_state = tl(state) + response = new_state + {:reply, response, new_state} end end ``` diff --git a/exercises/supervised_stack.livemd b/exercises/supervised_stack.livemd index b6db113b5..59fe5303a 100644 --- a/exercises/supervised_stack.livemd +++ b/exercises/supervised_stack.livemd @@ -104,7 +104,14 @@ children = [ Keep in mind, if you have already started a supervisor with the `Stack` process, your livebook may crash. You can resolve this issue by simply re-running the cell below to start the supervisor again. ```elixir +children = [ + %{ + id: Stack, + start: {Stack, :start_link, [[]]} + } +] +{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one) ``` You should be able to send a `:pop` message to the `Stack` process and the [Supervisor](https://hexdocs.pm/elixir/Supervisor.html) will restart the `Stack` process. @@ -112,7 +119,7 @@ You should be able to send a `:pop` message to the `Stack` process and the [Supe Uncomment and evaluate the code below to test your supervisor. ```elixir -# GenServer.call(Stack, :pop) +GenServer.call(Stack, :pop) ``` ## Mark As Completed diff --git a/exercises/task_drills.livemd b/exercises/task_drills.livemd index 900020104..fc9340b52 100644 --- a/exercises/task_drills.livemd +++ b/exercises/task_drills.livemd @@ -25,19 +25,27 @@ This set of drills is for [Tasks](../reading/task.livemd). Follow the instructio Use [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1) and [Task.await/2](https://hexdocs.pm/elixir/Task.html#await/2) to spawn a task process that returns any response. ```elixir +Kino.Process.render_seq_trace(fn -> + Process.sleep(2000) + Task.async(fn -> IO.puts("hello world!") end) +end) +# task = Task.async(fn -> Process.sleep(2000) end) +# Task.await(task) ``` Use [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1) and [Task.await/2](https://hexdocs.pm/elixir/Task.html#await/2) to spawn a task process that sleeps for six seconds. It should cause a timeout error. ```elixir - +task = Task.async(fn -> Process.sleep(6000) end) +Task.await(task) ``` Use [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1) and [Task.await/2](https://hexdocs.pm/elixir/Task.html#await/2) to spawn a task process that sleeps for six seconds. Provide a timeout value to [Task.await/2](https://hexdocs.pm/elixir/Task.html#await/2) so that it properly awaits the response. ```elixir - +task = Task.async(fn -> Process.sleep(2000) end) +Task.await(task, 7000) ``` ## Task.yield/2 @@ -55,7 +63,7 @@ time ``` ```elixir -{time, _result} = +Kino.Process.render_seq_trace(fn -> :timer.tc(fn -> # 1000 tasks # 1000 operations -> parallel @@ -69,9 +77,25 @@ time Task.await_many(tasks) end) +end) -10_009_225 -1_001_494 +# {time, _result} = +# :timer.tc(fn -> +# # 1000 tasks +# # 1000 operations -> parallel +# tasks = +# Enum.map(1..10, fn int -> +# Task.async(fn -> +# IO.puts("starting task #{int}") +# Process.sleep(1000) +# end) +# end) + +# Task.await_many(tasks) +# end) + +# 10_009_225 +# 1_001_494 ``` ### Fast Operation @@ -83,31 +107,55 @@ time ``` ```elixir -{time, _result} = +Kino.Process.render_seq_trace(fn -> :timer.tc(fn -> # 1000 tasks # 1000 operations -> parallel tasks = Enum.map(1..10, fn int -> Task.async(fn -> int * 2 end) end) Task.await_many(tasks) end) +end) + +# {time, _result} = +# :timer.tc(fn -> +# # 1000 tasks +# # 1000 operations -> parallel +# tasks = Enum.map(1..10, fn int -> Task.async(fn -> int * 2 end) end) +# Task.await_many(tasks) +# end) ``` Use [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1) and [Task.yield/2](https://hexdocs.pm/elixir/Task.html#yield/2) to spawn a task process that sleeps for six seconds and returns any response. Notice that the yield is `nil`. ```elixir +task = + Task.async(fn -> + Process.sleep(6000) + IO.puts("howdy!") + end) +Task.yield(task) ``` Use [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1) and [Task.yield/2](https://hexdocs.pm/elixir/Task.html#yield/2) to spawn a task process that sleeps for six seconds and returns any response. Provide a timeout value to [Task.yield/2](https://hexdocs.pm/elixir/Task.html#yield/2) so that it properly returns the response. ```elixir +task = + Task.async(fn -> + Process.sleep(6000) + IO.puts("howdy!") + end) +Task.yield(task, 7000) ``` Use [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1), [Task.yield/2](https://hexdocs.pm/elixir/Task.html#yield/2), and [Task.shutdown/2](https://hexdocs.pm/elixir/Task.html#shutdown/2) to spawn a task process that sleeps for six seconds. Shutdown the task process if it does not yield any value. ```elixir - +Kino.Process.render_seq_trace(fn -> + task = Task.async(fn -> Process.sleep(6000) end) + Task.yield(task) || Task.shutdown(task) +end) ``` ## Task.await_many/2 @@ -115,25 +163,37 @@ Use [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1), [Task.yield/2]( Use [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1) and [Task.await_many/2](https://hexdocs.pm/elixir/Task.html#await_many/2) to spawn two task processes that both return a random number from `1..10`. ```elixir +tasks = [ + Task.async(fn -> :rand.uniform(10) end), + Task.async(fn -> :rand.uniform(10) end) +] +Task.await_many(tasks) ``` Use [Enum.map/2](https://hexdocs.pm/elixir/Enum.html#map/2), [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1), and [Task.await_many/2](https://hexdocs.pm/elixir/Task.html#await_many/2) to spawn one hundred task processes that all return a random integer from `1..100`. ```elixir - +tasks = Enum.map(1..100, fn _num -> Task.async(fn -> :rand.uniform(100) end) end) +Task.await_many(tasks) ``` Use [Enum.map/2](https://hexdocs.pm/elixir/Enum.html#map/2), [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1), and [Task.await_many/2](https://hexdocs.pm/elixir/Task.html#await_many/2) to spawn one hundred task processes that return a list doubled numbers from one to one hundred. i.e. `[2, 4, 6, 8, ..., 198, 200]`. ```elixir - +tasks = Enum.map(1..100, fn int -> Task.async(fn -> int * 2 end) end) +Task.await_many(tasks) ``` Use [Enum.map/2](https://hexdocs.pm/elixir/Enum.html#map/2), [Task.async/1](https://hexdocs.pm/elixir/Task.html#async/1), [String.upcase/2](https://hexdocs.pm/elixir/String.html#upcase/2), and [Task.await_many/2](https://hexdocs.pm/elixir/Task.html#await_many/2) to concurrently capitalize every string in the list `["apple", "pear", "peaches"]` ```elixir +tasks = + Enum.map(["apple", "pear", "peaches"], fn string -> + Task.async(fn -> String.capitalize(string) end) + end) +Task.await_many(tasks) ``` ## Task.Supervisor diff --git a/exercises/tic-tac-toe.livemd b/exercises/tic-tac-toe.livemd index 08c12950f..bafb9544f 100644 --- a/exercises/tic-tac-toe.livemd +++ b/exercises/tic-tac-toe.livemd @@ -153,7 +153,13 @@ defmodule TicTacToe do iex> TicTacToe.at(board, {2, 2}) "C" """ + def at(board, coordinate) do + {x, y} = coordinate + + Enum.reverse(board) + |> Enum.at(y) + |> Enum.at(x) end @doc """ @@ -177,6 +183,15 @@ defmodule TicTacToe do [[nil, "X", "O"], [nil, "X", "O"],[nil, nil, "X"]] """ def fill(board, coordinate, symbol) do + {x, y} = coordinate + + row_index = 2 - y + + row = Enum.at(board, row_index) + + new_row = List.replace_at(row, x, symbol) + + List.replace_at(board, row_index, new_row) end end ``` diff --git a/exercises/time_converting.livemd b/exercises/time_converting.livemd index 5346dbbf2..578aeae68 100644 --- a/exercises/time_converting.livemd +++ b/exercises/time_converting.livemd @@ -74,6 +74,12 @@ defmodule TimeConverter do """ def to_seconds(amount, unit) do + case unit do + :seconds -> amount + :minutes -> amount * 60 + :hours -> amount * 3600 + :days -> amount * 86400 + end end @doc """ @@ -82,8 +88,8 @@ defmodule TimeConverter do ## Examples - iex> TimeConverter.from_seconds(1, :seconds) - 1.0 + iex> TimeConverter.from_seconds(2, :seconds) + 2.0 iex> TimeConverter.from_seconds(60, :minutes) 1.0 @@ -96,6 +102,12 @@ defmodule TimeConverter do """ def from_seconds(amount, unit) do + case unit do + :seconds -> amount / 1.0 + :minutes -> amount / 60 + :hours -> amount / 3600 + :days -> amount / 86400 + end end end ``` diff --git a/exercises/timeline.livemd b/exercises/timeline.livemd index 5bb45c981..6cafbd278 100644 --- a/exercises/timeline.livemd +++ b/exercises/timeline.livemd @@ -96,6 +96,14 @@ defmodule Timeline do [9, 12, 2] """ def from_dates(dates) do + # chunk the date list into pairs with offset = 1 + # map over the chunked pairs with Date.diff + + pairs = Enum.chunk_every(dates, 2, 1, :discard) + + Enum.map(pairs, fn [date1, date2] -> + Date.diff(date2, date1) + end) end @doc """ @@ -113,11 +121,31 @@ defmodule Timeline do iex> Timeline.from_strings(["2020-01-01", "2020-01-10", "2020-01-22", "2020-01-24"]) [9, 12, 2] """ + + def make_date_from_string(string) do + [year, month, day] = String.split(string, "-", trim: true) + Date.new!(String.to_integer(year), String.to_integer(month), String.to_integer(day)) + end + def from_strings(date_strings) do + # convert list of strings to new list of dates + # call from_dates with new list of dates + + new_list = Enum.map(date_strings, fn string -> make_date_from_string(string) end) + from_dates(new_list) end end ``` +```elixir +test_string = ["2020-01-01", "2020-01-10", "2020-01-22", "2020-01-24"] + +Enum.map(test_string, fn string -> + [year, month, day] = String.split(string, "-", trim: true) + Date.new!(String.to_integer(year), String.to_integer(month), String.to_integer(day)) +end) +``` + ## Mark As Completed diff --git a/exercises/timer.livemd b/exercises/timer.livemd index c51b45286..e823abdda 100644 --- a/exercises/timer.livemd +++ b/exercises/timer.livemd @@ -76,37 +76,66 @@ end Implement the `Timer` module as documented below. You will also have to implement the necessary [GenServer](https://hexdocs.pm/elixir/GenServer.html) callback functions such as [GenServer.init/1](https://hexdocs.pm/elixir/GenServer.html#init/1), [GenServer.handle_info/2](https://hexdocs.pm/elixir/GenServer.html#handle_info/2), and [GenServer.handle_call/3](https://hexdocs.pm/elixir/GenServer.html#handle_call/3). ```elixir - defmodule Timer do - @moduledoc """ - Documentation for `Timer` - """ - use GenServer - - @doc """ - Start the `Timer` process. - - ## Examples - - iex> {:ok, pid} = Timer.start_link([]) - """ - def start_link(_opts) do - end - - @doc """ - Get the number of seconds that have elapsed since the `Timer` was started. - - ## Examples - - iex> {:ok, pid} = Timer.start_link([]) - iex> Timer.seconds(pid) - 0 - iex> Process.sleep(1200) - iex> Timer.seconds(pid) - 1 - """ - def seconds(timer_pid) do - end - end +defmodule Timer do + @moduledoc """ + Documentation for `Timer` + """ + use GenServer + + @doc """ + Start the `Timer` process. + + ## Examples + + iex> {:ok, pid} = Timer.start_link([]) + """ + + ##### CLIENT ##### + + def start_link(_opts) do + GenServer.start_link(__MODULE__, []) + end + + @doc """ + Get the number of seconds that have elapsed since the `Timer` was started. + + ## Examples + + iex> {:ok, pid} = Timer.start_link([]) + iex> Timer.seconds(pid) + 0 + iex> Process.sleep(1200) + iex> Timer.seconds(pid) + 1 + """ + def seconds(pid) do + GenServer.call(pid, :seconds) + end + + ##### SERVER ##### + + @impl true + def init(_opts) do + increment_timer() + {:ok, 0} + end + + @impl true + def handle_call(:seconds, _from, state) do + {:reply, state, state} + end + + @impl true + def handle_info(:increment, state) do + increment_timer() + {:noreply, state + 1} + end + + defp increment_timer do + # add one second + Process.send_after(self(), :increment, 1000) + end +end ``` ## Bonus :timer @@ -157,16 +186,58 @@ defmodule AlternateTimer do @doc """ Start the `Timer` process. + + ## Examples + + iex> {:ok, pid} = Timer.start_link([]) """ + + ##### CLIENT ##### + def start_link(_opts) do GenServer.start_link(__MODULE__, []) end @doc """ Get the number of seconds that have elapsed since the `Timer` was started. + + ## Examples + + iex> {:ok, pid} = Timer.start_link([]) + iex> Timer.seconds(pid) + 0 + iex> Process.sleep(1200) + iex> Timer.seconds(pid) + 1 + """ - def seconds(timer_pid) do - GenServer.call(timer_pid, :seconds) + + def seconds(pid) do + GenServer.call(pid, :seconds) + end + + ##### SERVER ##### + + @impl true + def init(_opts) do + increment_timer() + {:ok, 0} + end + + @impl true + def handle_call(:seconds, _from, state) do + {:reply, state, state} + end + + @impl true + def handle_info(:increment, state) do + increment_timer() + {:noreply, state + 1} + end + + defp increment_timer do + # add one second + :timer.send_interval(1000, self(), :increment) end end ``` diff --git a/exercises/treasure_matching.livemd b/exercises/treasure_matching.livemd index 8995e1bf2..854e2a4aa 100644 --- a/exercises/treasure_matching.livemd +++ b/exercises/treasure_matching.livemd @@ -27,79 +27,98 @@ jewel Use pattern matching to bind a `jewel` variable to the `"jewel"` string. ```elixir -[1, 2, 3, "jewel"] +[_, _, _, jewel] = [1, 2, 3, "jewel"] +jewel ``` ```elixir -%{key1: "value", key2: "jewel"} +%{key1: value, key2: jewel} = %{key1: "value", key2: "jewel"} +jewel ``` ```elixir -%{1 => "jewel"} +%{1 => jewel} = %{1 => "jewel"} +jewel ``` ```elixir -%{%{key: [1, 2, 3, 4, 5, {}]} => "jewel"} +%{%{key: [1, 2, 3, 4, 5, {}]} => jewel} = %{%{key: [1, 2, 3, 4, 5, {}]} => "jewel"} +jewel ``` ```elixir -%{north: %{south: %{west: %{east: "jewel"}}}} +%{north: %{south: %{west: %{east: jewel}}}} = %{north: %{south: %{west: %{east: "jewel"}}}} +jewel ``` ```elixir -[2, "jewel"] +[_, jewel] = [2, "jewel"] +jewel ``` ```elixir -["jewel", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +[jewel | tail] = ["jewel", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +jewel ``` ```elixir -[1, "jewel", 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +[_, jewel | tail] = [1, "jewel", 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +jewel ``` ```elixir -[1, 2, "jewel", 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +[_, _, jewel | tail] = [1, 2, "jewel", 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +jewel ``` ```elixir -[[], [1, [2, "jewel"]]] +[_, [_, [_ | jewel]]] = [[], [1, [2, "jewel"]]] +jewel ``` ```elixir -"here is the jewel" +"here is the " <> jewel = "here is the jewel" +jewel ``` ```elixir -{"jewel"} +{jewel} = {"jewel"} +jewel ``` ```elixir -{"jewel", 1} +{jewel, _} = {"jewel", 1} +jewel ``` ```elixir -{1, 2, "jewel"} +{_, _, jewel} = {1, 2, "jewel"} +jewel ``` ```elixir -["jewel"] ++ Enum.to_list(1..100) +[jewel | tail] = ["jewel"] ++ Enum.to_list(1..100) +jewel ``` ```elixir -[key: "jewel"] +[key: jewel] = [key: "jewel"] +jewel ``` ```elixir -[south: "jewel", east: {1, 2}] +[{:south, jewel} | tail] = [south: "jewel", east: {1, 2}, north: "two"] +jewel ``` ```elixir -Enum.map(1..4, fn each -> (each == 2 && "jewel") || each end) +[_, jewel | tail] = Enum.map(1..4, fn each -> (each == 2 && "jewel") || each end) +jewel ``` ```elixir -Enum.map(1..4, &((&1 > 3 && "jewel") || &1)) +[_, _, _, jewel] = Enum.map(1..4, &((&1 > 3 && "jewel") || &1)) +jewel ``` ## Mark As Completed diff --git a/exercises/typespec_drills.livemd b/exercises/typespec_drills.livemd index 3ed091780..faaa46eee 100644 --- a/exercises/typespec_drills.livemd +++ b/exercises/typespec_drills.livemd @@ -157,72 +157,89 @@ end ```elixir defmodule FunctionSpecs do + @spec do_nothing :: nil def do_nothing do nil end + @spec accept_and_return_anything(any()) :: any() def accept_and_return_anything(anything) do anything end + @spec double(float()) :: float() def double(float) when is_float(float) do float * 2.0 end + @spec double(integer()) :: integer() def double(integer) when is_integer(integer) do integer * 2 end + @spec double(number()) :: number() def double(number) do number * 2 end + @spec add(integer(), integer()) :: integer() def add(integer1, integer2) do integer1 + integer2 end + @spec multiply(integer(), integer()) :: integer() def multiply(integer1, integer2) do integer1 * integer2 end + @spec divide(integer(), integer()) :: float() def divide(integer1, integer2) do integer1 / integer2 end + @spec rounded_divide(integer(), integer()) :: integer() def rounded_divide(integer1, integer2) do div(integer1, integer2) end + @spec concat(String.t(), String.t()) :: String.t() def concat(string1, string2) do string1 <> string2 end + @spec to_string(integer()) :: String.t() def to_string(integer) do Integer.to_string(integer) end + @spec merge(map(), map()) :: map() def merge(map1, map2) do Map.merge(map1, map2) end + @spec get_or_empty(Keyword.t(), atom()) :: any() def get_or_empty(keyword_list, atom_key) do Keyword.get(keyword_list, atom_key, "") end + @spec split_and_lowercase(String.t()) :: [String.t()] def split_and_lowercase(string) do string |> String.downcase() |> String.split("", trim: true) end + @spec string_to_int(String.t()) :: integer() def string_to_int(string) do String.to_integer(string) end + @spec integers_to_strings([integer()]) :: [String.t()] def integers_to_strings(integers) do Enum.map(integers, fn int -> Integer.to_string(int) end) end + @spec one_to_two(1) :: 2 def one_to_two(1) do 2 end @@ -254,13 +271,13 @@ end ```elixir defmodule CustomTypes do # a string or number - @type unparsed_number + @type unparsed_number :: String.t() | number() # a list of strings - @type strings + @type strings :: [String.t()] # a map with :title (string) and :content (string) keys. - @type book + @type book :: %{title: String.t(), content: String.t()} # A map with :name (string) and `:books` (a list of books) keys. - @type author + @type author :: %{name: String.t(), books: [book()]} end ``` diff --git a/exercises/weighted_voting.livemd b/exercises/weighted_voting.livemd index 96eb35039..166bb852c 100644 --- a/exercises/weighted_voting.livemd +++ b/exercises/weighted_voting.livemd @@ -82,12 +82,21 @@ defmodule WeightedVoting do iex> WeightedVoting.count([dogs: 20, dogs: 10], :dogs) 30 + iex> WeightedVoting.count([cats: 10, dogs: 20, dogs: 30], :dogs) 50 + iex> WeightedVoting.count([cats: 10, dogs: 20, dogs: 10, cats: 30], :cats) 40 """ def count(votes, vote_to_count) do + Enum.reduce(votes, 0, fn {key, value}, acc -> + if key == vote_to_count do + acc + value + else + acc + end + end) end @doc """ @@ -100,16 +109,35 @@ defmodule WeightedVoting do iex> WeightedVoting.tally([dogs: 20, dogs: 10]) [dogs: 30] + iex> WeightedVoting.tally([cats: 10, dogs: 20, dogs: 10]) [cats: 10, dogs: 30] + iex> WeightedVoting.tally([cats: 10, dogs: 20, cats: 20, dogs: 10, birds: 20]) [birds: 20, cats: 30, dogs: 30] """ def tally(votes) do + Enum.reduce(votes, [], fn {animal, value}, acc -> + Keyword.update(acc, animal, value, fn existing_value -> + value + existing_value + end) + end) + |> Enum.sort() end end ``` +```elixir +votes = [cats: 10, dogs: 20, cats: 20, dogs: 10, birds: 20] +vote_to_count = :birds + +def count(votes, vote_to_count) do + Enum.reduce(votes, fn acc, vote_to_count -> + IO.inspect(binding()) + end) +end +``` + ### Bonus: Tally Map Create a `WeightedVoting.tally_map/1` function which returns a map instead of a keyword list. diff --git a/exercises/with_points.livemd b/exercises/with_points.livemd index a6cf47a66..86a33af07 100644 --- a/exercises/with_points.livemd +++ b/exercises/with_points.livemd @@ -73,6 +73,13 @@ end Enter your solution below. +```elixir +%{team1_name: team1, team2_name: team2} +%{round1: %{team1: team1r1, team2: team2r1}} +%{round2: %{team1: team1r2, team2: team2r2}} +%{round3: %{team1: team1r3, team2: team2r3}} +``` + ```elixir defmodule Points do @doc """ @@ -96,15 +103,15 @@ defmodule Points do """ def tally(game) do with %{team1_name: team1, team2_name: team2} <- game, - %{round1: %{team1: t1r1, team2: t2r1}} <- game, - %{round2: %{team1: t1r2, team2: t2r2}} <- game, - %{round3: %{team1: t1r3, team2: t2r3}} <- game do + %{round1: %{team1: team1r1, team2: team2r1}} <- game, + %{round2: %{team1: team1r2, team2: team2r2}} <- game, + %{round3: %{team1: team1r3, team2: team2r3}} <- game do %{} - |> Map.put(team1, t1r1 + t1r2 + t1r3) - |> Map.put(team2, t2r1 + t2r2 + t2r3) + # IO.inspect(binding()) + |> Map.put(team1, team1r1 + team1r2 + team1r3) + |> Map.put(team2, team2r1 + team2r2 + team2r3) else - error -> - {:error, :invalid} + _error -> {:error, :invalid} end end end diff --git a/phoenix-1.6/counter/.formatter.exs b/phoenix-1.6/counter/.formatter.exs new file mode 100644 index 000000000..47616780b --- /dev/null +++ b/phoenix-1.6/counter/.formatter.exs @@ -0,0 +1,4 @@ +[ + import_deps: [:phoenix], + inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/phoenix-1.6/counter/.gitignore b/phoenix-1.6/counter/.gitignore new file mode 100644 index 000000000..80a831f9e --- /dev/null +++ b/phoenix-1.6/counter/.gitignore @@ -0,0 +1,34 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +counter-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + diff --git a/phoenix-1.6/counter/README.md b/phoenix-1.6/counter/README.md new file mode 100644 index 000000000..e5ab794d8 --- /dev/null +++ b/phoenix-1.6/counter/README.md @@ -0,0 +1,18 @@ +# Counter + +To start your Phoenix server: + + * Install dependencies with `mix deps.get` + * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + + * Official website: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/phoenix-1.6/counter/assets/css/app.css b/phoenix-1.6/counter/assets/css/app.css new file mode 100644 index 000000000..19c2e51ed --- /dev/null +++ b/phoenix-1.6/counter/assets/css/app.css @@ -0,0 +1,120 @@ +/* This file is for your main application CSS */ +@import "./phoenix.css"; + +/* Alerts and form errors used by phx.new */ +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert p { + margin-bottom: 0; +} +.alert:empty { + display: none; +} +.invalid-feedback { + color: #a94442; + display: block; + margin: -1rem 0 2rem; +} + +/* LiveView specific classes for your customization */ +.phx-no-feedback.invalid-feedback, +.phx-no-feedback .invalid-feedback { + display: none; +} + +.phx-click-loading { + opacity: 0.5; + transition: opacity 1s ease-out; +} + +.phx-loading{ + cursor: wait; +} + +.phx-modal { + opacity: 1!important; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.4); +} + +.phx-modal-content { + background-color: #fefefe; + margin: 15vh auto; + padding: 20px; + border: 1px solid #888; + width: 80%; +} + +.phx-modal-close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; +} + +.phx-modal-close:hover, +.phx-modal-close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + +.fade-in-scale { + animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys; +} + +.fade-out-scale { + animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys; +} + +.fade-in { + animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys; +} +.fade-out { + animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys; +} + +@keyframes fade-in-scale-keys{ + 0% { scale: 0.95; opacity: 0; } + 100% { scale: 1.0; opacity: 1; } +} + +@keyframes fade-out-scale-keys{ + 0% { scale: 1.0; opacity: 1; } + 100% { scale: 0.95; opacity: 0; } +} + +@keyframes fade-in-keys{ + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +@keyframes fade-out-keys{ + 0% { opacity: 1; } + 100% { opacity: 0; } +} diff --git a/phoenix-1.6/counter/assets/css/phoenix.css b/phoenix-1.6/counter/assets/css/phoenix.css new file mode 100644 index 000000000..0d59050f8 --- /dev/null +++ b/phoenix-1.6/counter/assets/css/phoenix.css @@ -0,0 +1,101 @@ +/* Includes some default style for the starter application. + * This can be safely deleted to start fresh. + */ + +/* Milligram v1.4.1 https://milligram.github.io + * Copyright (c) 2020 CJ Patoilo Licensed under the MIT license + */ + +*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#000000;font-family:'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#0069d9;border:0.1rem solid #0069d9;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#0069d9;border-color:#0069d9}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#0069d9}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#0069d9}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#0069d9}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#0069d9}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #0069d9;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='color'],input[type='date'],input[type='datetime'],input[type='datetime-local'],input[type='email'],input[type='month'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],input[type='week'],input:not([type]),textarea,select{-webkit-appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem .7rem;width:100%}input[type='color']:focus,input[type='date']:focus,input[type='datetime']:focus,input[type='datetime-local']:focus,input[type='email']:focus,input[type='month']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,input[type='week']:focus,input:not([type]):focus,textarea:focus,select:focus{border-color:#0069d9;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}select[multiple]{background:none;height:auto}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-40{margin-left:40%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-60{margin-left:60%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#0069d9;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;display:block;overflow-x:auto;text-align:left;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}@media (min-width: 40rem){table{display:table;overflow-x:initial}}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right} + +/* General style */ +h1{font-size: 3.6rem; line-height: 1.25} +h2{font-size: 2.8rem; line-height: 1.3} +h3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35} +h4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5} +h5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4} +h6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2} +pre{padding: 1em;} + +.container{ + margin: 0 auto; + max-width: 80.0rem; + padding: 0 2.0rem; + position: relative; + width: 100% +} +select { + width: auto; +} + +/* Phoenix promo and logo */ +.phx-hero { + text-align: center; + border-bottom: 1px solid #e3e3e3; + background: #eee; + border-radius: 6px; + padding: 3em 3em 1em; + margin-bottom: 3rem; + font-weight: 200; + font-size: 120%; +} +.phx-hero input { + background: #ffffff; +} +.phx-logo { + min-width: 300px; + margin: 1rem; + display: block; +} +.phx-logo img { + width: auto; + display: block; +} + +/* Headers */ +header { + width: 100%; + background: #fdfdfd; + border-bottom: 1px solid #eaeaea; + margin-bottom: 2rem; +} +header section { + align-items: center; + display: flex; + flex-direction: column; + justify-content: space-between; +} +header section :first-child { + order: 2; +} +header section :last-child { + order: 1; +} +header nav ul, +header nav li { + margin: 0; + padding: 0; + display: block; + text-align: right; + white-space: nowrap; +} +header nav ul { + margin: 1rem; + margin-top: 0; +} +header nav a { + display: block; +} + +@media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */ + header section { + flex-direction: row; + } + header nav ul { + margin: 1rem; + } + .phx-logo { + flex-basis: 527px; + margin: 2rem 1rem; + } +} diff --git a/phoenix-1.6/counter/assets/js/app.js b/phoenix-1.6/counter/assets/js/app.js new file mode 100644 index 000000000..2ca06a566 --- /dev/null +++ b/phoenix-1.6/counter/assets/js/app.js @@ -0,0 +1,45 @@ +// We import the CSS which is extracted to its own file by esbuild. +// Remove this line if you add a your own CSS build pipeline (e.g postcss). +import "../css/app.css" + +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import topbar from "../vendor/topbar" + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", info => topbar.show()) +window.addEventListener("phx:page-loading-stop", info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + diff --git a/phoenix-1.6/counter/assets/vendor/topbar.js b/phoenix-1.6/counter/assets/vendor/topbar.js new file mode 100644 index 000000000..1f6220974 --- /dev/null +++ b/phoenix-1.6/counter/assets/vendor/topbar.js @@ -0,0 +1,157 @@ +/** + * @license MIT + * topbar 1.0.0, 2021-01-06 + * https://buunguyen.github.io/topbar + * Copyright (c) 2021 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + // https://gist.github.com/paulirish/1579671 + (function () { + var lastTime = 0; + var vendors = ["ms", "moz", "webkit", "o"]; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x] + "RequestAnimationFrame"]; + window.cancelAnimationFrame = + window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"]; + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + })(); + + var canvas, + progressTimerId, + fadeTimerId, + currentProgress, + showing, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + document.body.appendChild(canvas); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function () { + if (showing) return; + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/phoenix-1.6/counter/config/config.exs b/phoenix-1.6/counter/config/config.exs new file mode 100644 index 000000000..1f394a0da --- /dev/null +++ b/phoenix-1.6/counter/config/config.exs @@ -0,0 +1,49 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +# Configures the endpoint +config :counter, CounterWeb.Endpoint, + url: [host: "localhost"], + render_errors: [view: CounterWeb.ErrorView, accepts: ~w(html json), layout: false], + pubsub_server: Counter.PubSub, + live_view: [signing_salt: "rKAUFyvf"] + +# Configures the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :counter, Counter.Mailer, adapter: Swoosh.Adapters.Local + +# Swoosh API client is needed for adapters other than SMTP. +config :swoosh, :api_client, false + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.14.29", + default: [ + args: + ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/phoenix-1.6/counter/config/dev.exs b/phoenix-1.6/counter/config/dev.exs new file mode 100644 index 000000000..205c62996 --- /dev/null +++ b/phoenix-1.6/counter/config/dev.exs @@ -0,0 +1,65 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we use it +# with esbuild to bundle .js and .css sources. +config :counter, CounterWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "KqEXpYgmN4n1J96fDa1WBaYTpLCk2ujKcoFEl9ykCvbPinJZ00n7f9prtjyNPsEK", + watchers: [ + # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args) + esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Note that this task requires Erlang/OTP 20 or later. +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :counter, CounterWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/counter_web/(live|views)/.*(ex)$", + ~r"lib/counter_web/templates/.*(eex)$" + ] + ] + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/phoenix-1.6/counter/config/prod.exs b/phoenix-1.6/counter/config/prod.exs new file mode 100644 index 000000000..ceab72203 --- /dev/null +++ b/phoenix-1.6/counter/config/prod.exs @@ -0,0 +1,49 @@ +import Config + +# For production, don't forget to configure the url host +# to something meaningful, Phoenix uses this information +# when generating URLs. +# +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix phx.digest` task, +# which you should run after static files are built and +# before starting your production server. +config :counter, CounterWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" + +# Do not print debug messages in production +config :logger, level: :info + +# ## SSL Support +# +# To get SSL working, you will need to add the `https` key +# to the previous section and set your `:url` port to 443: +# +# config :counter, CounterWeb.Endpoint, +# ..., +# url: [host: "example.com", port: 443], +# https: [ +# ..., +# port: 443, +# cipher_suite: :strong, +# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), +# certfile: System.get_env("SOME_APP_SSL_CERT_PATH") +# ] +# +# The `cipher_suite` is set to `:strong` to support only the +# latest and more secure SSL ciphers. This means old browsers +# and clients may not be supported. You can set it to +# `:compatible` for wider support. +# +# `:keyfile` and `:certfile` expect an absolute path to the key +# and cert in disk or a relative path inside priv, for example +# "priv/ssl/server.key". For all supported SSL configuration +# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 +# +# We also recommend setting `force_ssl` in your endpoint, ensuring +# no data is ever sent via http, always redirecting to https: +# +# config :counter, CounterWeb.Endpoint, +# force_ssl: [hsts: true] +# +# Check `Plug.SSL` for all available options in `force_ssl`. diff --git a/phoenix-1.6/counter/config/runtime.exs b/phoenix-1.6/counter/config/runtime.exs new file mode 100644 index 000000000..5bb04b9be --- /dev/null +++ b/phoenix-1.6/counter/config/runtime.exs @@ -0,0 +1,68 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/counter start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :counter, CounterWeb.Endpoint, server: true +end + +if config_env() == :prod do + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + port = String.to_integer(System.get_env("PORT") || "4000") + + config :counter, CounterWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base + + # ## Configuring the mailer + # + # In production you need to configure the mailer to use a different adapter. + # Also, you may need to configure the Swoosh API client of your choice if you + # are not using SMTP. Here is an example of the configuration: + # + # config :counter, Counter.Mailer, + # adapter: Swoosh.Adapters.Mailgun, + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("MAILGUN_DOMAIN") + # + # For this example you need include a HTTP client required by Swoosh API client. + # Swoosh supports Hackney and Finch out of the box: + # + # config :swoosh, :api_client, Swoosh.ApiClient.Hackney + # + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. +end diff --git a/phoenix-1.6/counter/config/test.exs b/phoenix-1.6/counter/config/test.exs new file mode 100644 index 000000000..8ea329711 --- /dev/null +++ b/phoenix-1.6/counter/config/test.exs @@ -0,0 +1,18 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :counter, CounterWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "fRABlWiExUUFpX89Pgs1MNiWNvtvAmXWRfDUf0RuV0nq05DqfFjkw0Kf7nwBHmXS", + server: false + +# In test we don't send emails. +config :counter, Counter.Mailer, + adapter: Swoosh.Adapters.Test + +# Print only warnings and errors during test +config :logger, level: :warn + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/phoenix-1.6/counter/lib/counter.ex b/phoenix-1.6/counter/lib/counter.ex new file mode 100644 index 000000000..9151b8a24 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter.ex @@ -0,0 +1,36 @@ +defmodule Counter do + @moduledoc """ + Counter keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ + + use GenServer + + ##### CLIENT ##### + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts) + end + + def increment(pid) do + GenServer.call(pid, :increment) + end + + + ##### SERVER ##### + + @impl true + def init(init_args) do + {:ok, init_args} + end + + @impl true + def handle_call(:increment, _from, state) do + response = state + 1 + {:reply, response, state} + end + +end diff --git a/phoenix-1.6/counter/lib/counter/application.ex b/phoenix-1.6/counter/lib/counter/application.ex new file mode 100644 index 000000000..2dc9e0dc6 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter/application.ex @@ -0,0 +1,34 @@ +defmodule Counter.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + # Start the Telemetry supervisor + CounterWeb.Telemetry, + # Start the PubSub system + {Phoenix.PubSub, name: Counter.PubSub}, + # Start the Endpoint (http/https) + CounterWeb.Endpoint, + # Start a worker by calling: Counter.Worker.start_link(arg) + # {Counter.Worker, arg} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Counter.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + CounterWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/phoenix-1.6/counter/lib/counter/mailer.ex b/phoenix-1.6/counter/lib/counter/mailer.ex new file mode 100644 index 000000000..fa6f9cda4 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter/mailer.ex @@ -0,0 +1,3 @@ +defmodule Counter.Mailer do + use Swoosh.Mailer, otp_app: :counter +end diff --git a/phoenix-1.6/counter/lib/counter_web.ex b/phoenix-1.6/counter/lib/counter_web.ex new file mode 100644 index 000000000..4717d1a01 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web.ex @@ -0,0 +1,110 @@ +defmodule CounterWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use CounterWeb, :controller + use CounterWeb, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define any helper function in modules + and import those modules here. + """ + + def controller do + quote do + use Phoenix.Controller, namespace: CounterWeb + + import Plug.Conn + import CounterWeb.Gettext + alias CounterWeb.Router.Helpers, as: Routes + end + end + + def view do + quote do + use Phoenix.View, + root: "lib/counter_web/templates", + namespace: CounterWeb + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] + + # Include shared imports and aliases for views + unquote(view_helpers()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {CounterWeb.LayoutView, "live.html"} + + unquote(view_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(view_helpers()) + end + end + + def component do + quote do + use Phoenix.Component + + unquote(view_helpers()) + end + end + + def router do + quote do + use Phoenix.Router + + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + import CounterWeb.Gettext + end + end + + defp view_helpers do + quote do + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) + import Phoenix.LiveView.Helpers + + # Import basic rendering functionality (render, render_layout, etc) + import Phoenix.View + + import CounterWeb.ErrorHelpers + import CounterWeb.Gettext + alias CounterWeb.Router.Helpers, as: Routes + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/phoenix-1.6/counter/lib/counter_web/controllers/count_controller.ex b/phoenix-1.6/counter/lib/counter_web/controllers/count_controller.ex new file mode 100644 index 000000000..22ed1e013 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/controllers/count_controller.ex @@ -0,0 +1,7 @@ +defmodule CounterWeb.CountController do + use CounterWeb, :controller + + def count(conn, _params) do + render(conn, "count.html") + end +end diff --git a/phoenix-1.6/counter/lib/counter_web/controllers/page_controller.ex b/phoenix-1.6/counter/lib/counter_web/controllers/page_controller.ex new file mode 100644 index 000000000..da318d019 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/controllers/page_controller.ex @@ -0,0 +1,7 @@ +defmodule CounterWeb.PageController do + use CounterWeb, :controller + + def index(conn, _params) do + render(conn, "index.html") + end +end diff --git a/phoenix-1.6/counter/lib/counter_web/endpoint.ex b/phoenix-1.6/counter/lib/counter_web/endpoint.ex new file mode 100644 index 000000000..86eb5acce --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/endpoint.ex @@ -0,0 +1,49 @@ +defmodule CounterWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :counter + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_counter_key", + signing_salt: "ZPVJIBdR" + ] + + socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phx.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", + from: :counter, + gzip: false, + only: ~w(assets fonts images favicon.ico robots.txt) + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug CounterWeb.Router +end diff --git a/phoenix-1.6/counter/lib/counter_web/gettext.ex b/phoenix-1.6/counter/lib/counter_web/gettext.ex new file mode 100644 index 000000000..041b3cf02 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule CounterWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import CounterWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :counter +end diff --git a/phoenix-1.6/counter/lib/counter_web/router.ex b/phoenix-1.6/counter/lib/counter_web/router.ex new file mode 100644 index 000000000..8c7acc537 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/router.ex @@ -0,0 +1,57 @@ +defmodule CounterWeb.Router do + use CounterWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, {CounterWeb.LayoutView, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", CounterWeb do + pipe_through :browser + + get "/", PageController, :index + get "/count", CountController, :count + end + + # Other scopes may use custom stacks. + # scope "/api", CounterWeb do + # pipe_through :api + # end + + # Enables LiveDashboard only for development + # + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + if Mix.env() in [:dev, :test] do + import Phoenix.LiveDashboard.Router + + scope "/" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: CounterWeb.Telemetry + end + end + + # Enables the Swoosh mailbox preview in development. + # + # Note that preview only shows emails that were sent by the same + # node running the Phoenix server. + if Mix.env() == :dev do + scope "/dev" do + pipe_through :browser + + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end +end diff --git a/phoenix-1.6/counter/lib/counter_web/telemetry.ex b/phoenix-1.6/counter/lib/counter_web/telemetry.ex new file mode 100644 index 000000000..3157ba46c --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/telemetry.ex @@ -0,0 +1,48 @@ +defmodule CounterWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {CounterWeb, :count_users, []} + ] + end +end diff --git a/phoenix-1.6/counter/lib/counter_web/templates/count/count.html.heex b/phoenix-1.6/counter/lib/counter_web/templates/count/count.html.heex new file mode 100644 index 000000000..a3c5dce3a --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/templates/count/count.html.heex @@ -0,0 +1,2 @@ +Hello World +

Hello World

diff --git a/phoenix-1.6/counter/lib/counter_web/templates/layout/app.html.heex b/phoenix-1.6/counter/lib/counter_web/templates/layout/app.html.heex new file mode 100644 index 000000000..169aed956 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/templates/layout/app.html.heex @@ -0,0 +1,5 @@ +
+ + + <%= @inner_content %> +
diff --git a/phoenix-1.6/counter/lib/counter_web/templates/layout/live.html.heex b/phoenix-1.6/counter/lib/counter_web/templates/layout/live.html.heex new file mode 100644 index 000000000..a29d60448 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/templates/layout/live.html.heex @@ -0,0 +1,11 @@ +
+ + + + + <%= @inner_content %> +
diff --git a/phoenix-1.6/counter/lib/counter_web/templates/layout/root.html.heex b/phoenix-1.6/counter/lib/counter_web/templates/layout/root.html.heex new file mode 100644 index 000000000..bf8fd0ca3 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/templates/layout/root.html.heex @@ -0,0 +1,30 @@ + + + + + + + + <%= live_title_tag assigns[:page_title] || "Counter", suffix: " Ā· Phoenix Framework" %> + + + + +
+
+ + +
+
+ <%= @inner_content %> + + diff --git a/phoenix-1.6/counter/lib/counter_web/templates/page/index.html.heex b/phoenix-1.6/counter/lib/counter_web/templates/page/index.html.heex new file mode 100644 index 000000000..f844bd8d7 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/templates/page/index.html.heex @@ -0,0 +1,41 @@ +
+

<%= gettext "Welcome to %{name}!", name: "Phoenix" %>

+

Peace of mind from prototype to production

+
+ +
+
+

Resources

+ +
+
+

Help

+ +
+
diff --git a/phoenix-1.6/counter/lib/counter_web/views/count_view.ex b/phoenix-1.6/counter/lib/counter_web/views/count_view.ex new file mode 100644 index 000000000..e927b250c --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/views/count_view.ex @@ -0,0 +1,3 @@ +defmodule CounterWeb.CountView do + use CounterWeb, :view +end diff --git a/phoenix-1.6/counter/lib/counter_web/views/error_helpers.ex b/phoenix-1.6/counter/lib/counter_web/views/error_helpers.ex new file mode 100644 index 000000000..036693f3c --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/views/error_helpers.ex @@ -0,0 +1,47 @@ +defmodule CounterWeb.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + use Phoenix.HTML + + @doc """ + Generates tag for inlined form input errors. + """ + def error_tag(form, field) do + Enum.map(Keyword.get_values(form.errors, field), fn error -> + content_tag(:span, translate_error(error), + class: "invalid-feedback", + phx_feedback_for: input_name(form, field) + ) + end) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate "is invalid" in the "errors" domain + # dgettext("errors", "is invalid") + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + # This requires us to call the Gettext module passing our gettext + # backend as first argument. + # + # Note we use the "errors" domain, which means translations + # should be written to the errors.po file. The :count option is + # set by Ecto and indicates we should also apply plural rules. + if count = opts[:count] do + Gettext.dngettext(CounterWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(CounterWeb.Gettext, "errors", msg, opts) + end + end +end diff --git a/phoenix-1.6/counter/lib/counter_web/views/error_view.ex b/phoenix-1.6/counter/lib/counter_web/views/error_view.ex new file mode 100644 index 000000000..b3bc98a18 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/views/error_view.ex @@ -0,0 +1,16 @@ +defmodule CounterWeb.ErrorView do + use CounterWeb, :view + + # If you want to customize a particular status code + # for a certain format, you may uncomment below. + # def render("500.html", _assigns) do + # "Internal Server Error" + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.html" becomes + # "Not Found". + def template_not_found(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/phoenix-1.6/counter/lib/counter_web/views/layout_view.ex b/phoenix-1.6/counter/lib/counter_web/views/layout_view.ex new file mode 100644 index 000000000..1b84888b9 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/views/layout_view.ex @@ -0,0 +1,7 @@ +defmodule CounterWeb.LayoutView do + use CounterWeb, :view + + # Phoenix LiveDashboard is available only in development by default, + # so we instruct Elixir to not warn if the dashboard route is missing. + @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}} +end diff --git a/phoenix-1.6/counter/lib/counter_web/views/page_view.ex b/phoenix-1.6/counter/lib/counter_web/views/page_view.ex new file mode 100644 index 000000000..cf17b79e2 --- /dev/null +++ b/phoenix-1.6/counter/lib/counter_web/views/page_view.ex @@ -0,0 +1,3 @@ +defmodule CounterWeb.PageView do + use CounterWeb, :view +end diff --git a/phoenix-1.6/counter/mix.exs b/phoenix-1.6/counter/mix.exs new file mode 100644 index 000000000..63a45dbf7 --- /dev/null +++ b/phoenix-1.6/counter/mix.exs @@ -0,0 +1,64 @@ +defmodule Counter.MixProject do + use Mix.Project + + def project do + [ + app: :counter, + version: "0.1.0", + elixir: "~> 1.12", + elixirc_paths: elixirc_paths(Mix.env()), + compilers: [:gettext] ++ Mix.compilers(), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {Counter.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.6.15"}, + {:phoenix_html, "~> 3.0"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 0.17.5"}, + {:floki, ">= 0.30.0", only: :test}, + {:phoenix_live_dashboard, "~> 0.6"}, + {:esbuild, "~> 0.4", runtime: Mix.env() == :dev}, + {:swoosh, "~> 1.3"}, + {:telemetry_metrics, "~> 0.6"}, + {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 0.18"}, + {:jason, "~> 1.2"}, + {:plug_cowboy, "~> 2.5"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to install project dependencies and perform other setup tasks, run: + # + # $ mix setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get"], + "assets.deploy": ["esbuild default --minify", "phx.digest"] + ] + end +end diff --git a/phoenix-1.6/counter/mix.lock b/phoenix-1.6/counter/mix.lock new file mode 100644 index 000000000..47b126398 --- /dev/null +++ b/phoenix-1.6/counter/mix.lock @@ -0,0 +1,29 @@ +%{ + "castore": {:hex, :castore, "1.0.1", "240b9edb4e9e94f8f56ab39d8d2d0a57f49e46c56aced8f873892df8ff64ff5a", [:mix], [], "hexpm", "b4951de93c224d44fac71614beabd88b71932d0b1dea80d2f80fb9044e01bbb3"}, + "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, + "esbuild": {:hex, :esbuild, "0.6.1", "a774bfa7b4512a1211bf15880b462be12a4c48ed753a170c68c63b2c95888150", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "569f7409fb5a932211573fc20e2a930a0d5cf3377c5b4f6506c651b1783a1678"}, + "expo": {:hex, :expo, "0.4.0", "bbe4bf455e2eb2ebd2f1e7d83530ce50fb9990eb88fc47855c515bfdf1c6626f", [:mix], [], "hexpm", "a8ed1683ec8b7c7fa53fd7a41b2c6935f539168a6bb0616d7fd6b58a36f3abf2"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"}, + "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, + "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.14", "5ec615d4d61bf9d4755f158bd6c80372b715533fe6d6219e12d74fb5eedbeac1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "afeb6ba43ce329a6f7fc1c9acdfc6d3039995345f025febb7f409a92f6faebd3"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, + "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, +} diff --git a/phoenix-1.6/counter/priv/gettext/en/LC_MESSAGES/errors.po b/phoenix-1.6/counter/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 000000000..cdec3a113 --- /dev/null +++ b/phoenix-1.6/counter/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,11 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" diff --git a/phoenix-1.6/counter/priv/gettext/errors.pot b/phoenix-1.6/counter/priv/gettext/errors.pot new file mode 100644 index 000000000..d6f47fa87 --- /dev/null +++ b/phoenix-1.6/counter/priv/gettext/errors.pot @@ -0,0 +1,10 @@ +## This is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here has no +## effect: edit them in PO (`.po`) files instead. + diff --git a/phoenix-1.6/counter/priv/static/favicon.ico b/phoenix-1.6/counter/priv/static/favicon.ico new file mode 100644 index 000000000..73de524aa Binary files /dev/null and b/phoenix-1.6/counter/priv/static/favicon.ico differ diff --git a/phoenix-1.6/counter/priv/static/images/phoenix.png b/phoenix-1.6/counter/priv/static/images/phoenix.png new file mode 100644 index 000000000..9c81075f6 Binary files /dev/null and b/phoenix-1.6/counter/priv/static/images/phoenix.png differ diff --git a/phoenix-1.6/counter/priv/static/robots.txt b/phoenix-1.6/counter/priv/static/robots.txt new file mode 100644 index 000000000..26e06b5f1 --- /dev/null +++ b/phoenix-1.6/counter/priv/static/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/phoenix-1.6/counter/test/counter_web/controllers/page_controller_test.exs b/phoenix-1.6/counter/test/counter_web/controllers/page_controller_test.exs new file mode 100644 index 000000000..2165e3ce2 --- /dev/null +++ b/phoenix-1.6/counter/test/counter_web/controllers/page_controller_test.exs @@ -0,0 +1,8 @@ +defmodule CounterWeb.PageControllerTest do + use CounterWeb.ConnCase + + test "GET /", %{conn: conn} do + conn = get(conn, "/") + assert html_response(conn, 200) =~ "Welcome to Phoenix!" + end +end diff --git a/phoenix-1.6/counter/test/counter_web/views/error_view_test.exs b/phoenix-1.6/counter/test/counter_web/views/error_view_test.exs new file mode 100644 index 000000000..3e3c63e65 --- /dev/null +++ b/phoenix-1.6/counter/test/counter_web/views/error_view_test.exs @@ -0,0 +1,14 @@ +defmodule CounterWeb.ErrorViewTest do + use CounterWeb.ConnCase, async: true + + # Bring render/3 and render_to_string/3 for testing custom views + import Phoenix.View + + test "renders 404.html" do + assert render_to_string(CounterWeb.ErrorView, "404.html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(CounterWeb.ErrorView, "500.html", []) == "Internal Server Error" + end +end diff --git a/phoenix-1.6/counter/test/counter_web/views/layout_view_test.exs b/phoenix-1.6/counter/test/counter_web/views/layout_view_test.exs new file mode 100644 index 000000000..6e10cb911 --- /dev/null +++ b/phoenix-1.6/counter/test/counter_web/views/layout_view_test.exs @@ -0,0 +1,8 @@ +defmodule CounterWeb.LayoutViewTest do + use CounterWeb.ConnCase, async: true + + # When testing helpers, you may want to import Phoenix.HTML and + # use functions such as safe_to_string() to convert the helper + # result into an HTML string. + # import Phoenix.HTML +end diff --git a/phoenix-1.6/counter/test/counter_web/views/page_view_test.exs b/phoenix-1.6/counter/test/counter_web/views/page_view_test.exs new file mode 100644 index 000000000..5e85cd31d --- /dev/null +++ b/phoenix-1.6/counter/test/counter_web/views/page_view_test.exs @@ -0,0 +1,3 @@ +defmodule CounterWeb.PageViewTest do + use CounterWeb.ConnCase, async: true +end diff --git a/phoenix-1.6/counter/test/support/conn_case.ex b/phoenix-1.6/counter/test/support/conn_case.ex new file mode 100644 index 000000000..9fa719c2c --- /dev/null +++ b/phoenix-1.6/counter/test/support/conn_case.ex @@ -0,0 +1,37 @@ +defmodule CounterWeb.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use CounterWeb.ConnCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import CounterWeb.ConnCase + + alias CounterWeb.Router.Helpers, as: Routes + + # The default endpoint for testing + @endpoint CounterWeb.Endpoint + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/phoenix-1.6/counter/test/test_helper.exs b/phoenix-1.6/counter/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/phoenix-1.6/counter/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/phoenix-1.6/hello_world/.formatter.exs b/phoenix-1.6/hello_world/.formatter.exs new file mode 100644 index 000000000..47616780b --- /dev/null +++ b/phoenix-1.6/hello_world/.formatter.exs @@ -0,0 +1,4 @@ +[ + import_deps: [:phoenix], + inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/phoenix-1.6/hello_world/.gitignore b/phoenix-1.6/hello_world/.gitignore new file mode 100644 index 000000000..13311e695 --- /dev/null +++ b/phoenix-1.6/hello_world/.gitignore @@ -0,0 +1,34 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +hello_world-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + diff --git a/phoenix-1.6/hello_world/README.md b/phoenix-1.6/hello_world/README.md new file mode 100644 index 000000000..6f6319189 --- /dev/null +++ b/phoenix-1.6/hello_world/README.md @@ -0,0 +1,18 @@ +# HelloWorld + +To start your Phoenix server: + + * Install dependencies with `mix deps.get` + * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + + * Official website: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/phoenix-1.6/hello_world/assets/css/app.css b/phoenix-1.6/hello_world/assets/css/app.css new file mode 100644 index 000000000..19c2e51ed --- /dev/null +++ b/phoenix-1.6/hello_world/assets/css/app.css @@ -0,0 +1,120 @@ +/* This file is for your main application CSS */ +@import "./phoenix.css"; + +/* Alerts and form errors used by phx.new */ +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert p { + margin-bottom: 0; +} +.alert:empty { + display: none; +} +.invalid-feedback { + color: #a94442; + display: block; + margin: -1rem 0 2rem; +} + +/* LiveView specific classes for your customization */ +.phx-no-feedback.invalid-feedback, +.phx-no-feedback .invalid-feedback { + display: none; +} + +.phx-click-loading { + opacity: 0.5; + transition: opacity 1s ease-out; +} + +.phx-loading{ + cursor: wait; +} + +.phx-modal { + opacity: 1!important; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.4); +} + +.phx-modal-content { + background-color: #fefefe; + margin: 15vh auto; + padding: 20px; + border: 1px solid #888; + width: 80%; +} + +.phx-modal-close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; +} + +.phx-modal-close:hover, +.phx-modal-close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + +.fade-in-scale { + animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys; +} + +.fade-out-scale { + animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys; +} + +.fade-in { + animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys; +} +.fade-out { + animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys; +} + +@keyframes fade-in-scale-keys{ + 0% { scale: 0.95; opacity: 0; } + 100% { scale: 1.0; opacity: 1; } +} + +@keyframes fade-out-scale-keys{ + 0% { scale: 1.0; opacity: 1; } + 100% { scale: 0.95; opacity: 0; } +} + +@keyframes fade-in-keys{ + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +@keyframes fade-out-keys{ + 0% { opacity: 1; } + 100% { opacity: 0; } +} diff --git a/phoenix-1.6/hello_world/assets/css/phoenix.css b/phoenix-1.6/hello_world/assets/css/phoenix.css new file mode 100644 index 000000000..0d59050f8 --- /dev/null +++ b/phoenix-1.6/hello_world/assets/css/phoenix.css @@ -0,0 +1,101 @@ +/* Includes some default style for the starter application. + * This can be safely deleted to start fresh. + */ + +/* Milligram v1.4.1 https://milligram.github.io + * Copyright (c) 2020 CJ Patoilo Licensed under the MIT license + */ + +*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#000000;font-family:'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#0069d9;border:0.1rem solid #0069d9;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#0069d9;border-color:#0069d9}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#0069d9}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#0069d9}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#0069d9}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#0069d9}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #0069d9;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='color'],input[type='date'],input[type='datetime'],input[type='datetime-local'],input[type='email'],input[type='month'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],input[type='week'],input:not([type]),textarea,select{-webkit-appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem .7rem;width:100%}input[type='color']:focus,input[type='date']:focus,input[type='datetime']:focus,input[type='datetime-local']:focus,input[type='email']:focus,input[type='month']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,input[type='week']:focus,input:not([type]):focus,textarea:focus,select:focus{border-color:#0069d9;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}select[multiple]{background:none;height:auto}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-40{margin-left:40%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-60{margin-left:60%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#0069d9;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;display:block;overflow-x:auto;text-align:left;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}@media (min-width: 40rem){table{display:table;overflow-x:initial}}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right} + +/* General style */ +h1{font-size: 3.6rem; line-height: 1.25} +h2{font-size: 2.8rem; line-height: 1.3} +h3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35} +h4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5} +h5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4} +h6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2} +pre{padding: 1em;} + +.container{ + margin: 0 auto; + max-width: 80.0rem; + padding: 0 2.0rem; + position: relative; + width: 100% +} +select { + width: auto; +} + +/* Phoenix promo and logo */ +.phx-hero { + text-align: center; + border-bottom: 1px solid #e3e3e3; + background: #eee; + border-radius: 6px; + padding: 3em 3em 1em; + margin-bottom: 3rem; + font-weight: 200; + font-size: 120%; +} +.phx-hero input { + background: #ffffff; +} +.phx-logo { + min-width: 300px; + margin: 1rem; + display: block; +} +.phx-logo img { + width: auto; + display: block; +} + +/* Headers */ +header { + width: 100%; + background: #fdfdfd; + border-bottom: 1px solid #eaeaea; + margin-bottom: 2rem; +} +header section { + align-items: center; + display: flex; + flex-direction: column; + justify-content: space-between; +} +header section :first-child { + order: 2; +} +header section :last-child { + order: 1; +} +header nav ul, +header nav li { + margin: 0; + padding: 0; + display: block; + text-align: right; + white-space: nowrap; +} +header nav ul { + margin: 1rem; + margin-top: 0; +} +header nav a { + display: block; +} + +@media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */ + header section { + flex-direction: row; + } + header nav ul { + margin: 1rem; + } + .phx-logo { + flex-basis: 527px; + margin: 2rem 1rem; + } +} diff --git a/phoenix-1.6/hello_world/assets/js/app.js b/phoenix-1.6/hello_world/assets/js/app.js new file mode 100644 index 000000000..2ca06a566 --- /dev/null +++ b/phoenix-1.6/hello_world/assets/js/app.js @@ -0,0 +1,45 @@ +// We import the CSS which is extracted to its own file by esbuild. +// Remove this line if you add a your own CSS build pipeline (e.g postcss). +import "../css/app.css" + +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import topbar from "../vendor/topbar" + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", info => topbar.show()) +window.addEventListener("phx:page-loading-stop", info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + diff --git a/phoenix-1.6/hello_world/assets/vendor/topbar.js b/phoenix-1.6/hello_world/assets/vendor/topbar.js new file mode 100644 index 000000000..1f6220974 --- /dev/null +++ b/phoenix-1.6/hello_world/assets/vendor/topbar.js @@ -0,0 +1,157 @@ +/** + * @license MIT + * topbar 1.0.0, 2021-01-06 + * https://buunguyen.github.io/topbar + * Copyright (c) 2021 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + // https://gist.github.com/paulirish/1579671 + (function () { + var lastTime = 0; + var vendors = ["ms", "moz", "webkit", "o"]; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x] + "RequestAnimationFrame"]; + window.cancelAnimationFrame = + window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"]; + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + })(); + + var canvas, + progressTimerId, + fadeTimerId, + currentProgress, + showing, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + document.body.appendChild(canvas); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function () { + if (showing) return; + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/phoenix-1.6/hello_world/config/config.exs b/phoenix-1.6/hello_world/config/config.exs new file mode 100644 index 000000000..e4f88d7c4 --- /dev/null +++ b/phoenix-1.6/hello_world/config/config.exs @@ -0,0 +1,49 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +# Configures the endpoint +config :hello_world, HelloWorldWeb.Endpoint, + url: [host: "localhost"], + render_errors: [view: HelloWorldWeb.ErrorView, accepts: ~w(html json), layout: false], + pubsub_server: HelloWorld.PubSub, + live_view: [signing_salt: "MQVUsj5f"] + +# Configures the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :hello_world, HelloWorld.Mailer, adapter: Swoosh.Adapters.Local + +# Swoosh API client is needed for adapters other than SMTP. +config :swoosh, :api_client, false + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.14.29", + default: [ + args: + ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/phoenix-1.6/hello_world/config/dev.exs b/phoenix-1.6/hello_world/config/dev.exs new file mode 100644 index 000000000..491e9a5ea --- /dev/null +++ b/phoenix-1.6/hello_world/config/dev.exs @@ -0,0 +1,65 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we use it +# with esbuild to bundle .js and .css sources. +config :hello_world, HelloWorldWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "Q5wEsVNF2nz33JqGizo4fN+WW/RrB9n4D5t6hHq0CjMfvp0Kwl4qIaq4iF7Tc5oY", + watchers: [ + # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args) + esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Note that this task requires Erlang/OTP 20 or later. +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :hello_world, HelloWorldWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/hello_world_web/(live|views)/.*(ex)$", + ~r"lib/hello_world_web/templates/.*(eex)$" + ] + ] + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/phoenix-1.6/hello_world/config/prod.exs b/phoenix-1.6/hello_world/config/prod.exs new file mode 100644 index 000000000..1ffa41952 --- /dev/null +++ b/phoenix-1.6/hello_world/config/prod.exs @@ -0,0 +1,49 @@ +import Config + +# For production, don't forget to configure the url host +# to something meaningful, Phoenix uses this information +# when generating URLs. +# +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix phx.digest` task, +# which you should run after static files are built and +# before starting your production server. +config :hello_world, HelloWorldWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" + +# Do not print debug messages in production +config :logger, level: :info + +# ## SSL Support +# +# To get SSL working, you will need to add the `https` key +# to the previous section and set your `:url` port to 443: +# +# config :hello_world, HelloWorldWeb.Endpoint, +# ..., +# url: [host: "example.com", port: 443], +# https: [ +# ..., +# port: 443, +# cipher_suite: :strong, +# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), +# certfile: System.get_env("SOME_APP_SSL_CERT_PATH") +# ] +# +# The `cipher_suite` is set to `:strong` to support only the +# latest and more secure SSL ciphers. This means old browsers +# and clients may not be supported. You can set it to +# `:compatible` for wider support. +# +# `:keyfile` and `:certfile` expect an absolute path to the key +# and cert in disk or a relative path inside priv, for example +# "priv/ssl/server.key". For all supported SSL configuration +# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 +# +# We also recommend setting `force_ssl` in your endpoint, ensuring +# no data is ever sent via http, always redirecting to https: +# +# config :hello_world, HelloWorldWeb.Endpoint, +# force_ssl: [hsts: true] +# +# Check `Plug.SSL` for all available options in `force_ssl`. diff --git a/phoenix-1.6/hello_world/config/runtime.exs b/phoenix-1.6/hello_world/config/runtime.exs new file mode 100644 index 000000000..9498fc0f7 --- /dev/null +++ b/phoenix-1.6/hello_world/config/runtime.exs @@ -0,0 +1,68 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/hello_world start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :hello_world, HelloWorldWeb.Endpoint, server: true +end + +if config_env() == :prod do + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + port = String.to_integer(System.get_env("PORT") || "4000") + + config :hello_world, HelloWorldWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base + + # ## Configuring the mailer + # + # In production you need to configure the mailer to use a different adapter. + # Also, you may need to configure the Swoosh API client of your choice if you + # are not using SMTP. Here is an example of the configuration: + # + # config :hello_world, HelloWorld.Mailer, + # adapter: Swoosh.Adapters.Mailgun, + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("MAILGUN_DOMAIN") + # + # For this example you need include a HTTP client required by Swoosh API client. + # Swoosh supports Hackney and Finch out of the box: + # + # config :swoosh, :api_client, Swoosh.ApiClient.Hackney + # + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. +end diff --git a/phoenix-1.6/hello_world/config/test.exs b/phoenix-1.6/hello_world/config/test.exs new file mode 100644 index 000000000..5eaa121ce --- /dev/null +++ b/phoenix-1.6/hello_world/config/test.exs @@ -0,0 +1,18 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :hello_world, HelloWorldWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "VWT3k+lM0qw5c6ITamiuZSdLKFBV8W6TkPuxF6wq541G+rIPJZNOL9Yh+Du5WUsr", + server: false + +# In test we don't send emails. +config :hello_world, HelloWorld.Mailer, + adapter: Swoosh.Adapters.Test + +# Print only warnings and errors during test +config :logger, level: :warn + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/phoenix-1.6/hello_world/lib/hello_world.ex b/phoenix-1.6/hello_world/lib/hello_world.ex new file mode 100644 index 000000000..944ff14a0 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world.ex @@ -0,0 +1,9 @@ +defmodule HelloWorld do + @moduledoc """ + HelloWorld keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/phoenix-1.6/hello_world/lib/hello_world/application.ex b/phoenix-1.6/hello_world/lib/hello_world/application.ex new file mode 100644 index 000000000..d9d15329f --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world/application.ex @@ -0,0 +1,34 @@ +defmodule HelloWorld.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + # Start the Telemetry supervisor + HelloWorldWeb.Telemetry, + # Start the PubSub system + {Phoenix.PubSub, name: HelloWorld.PubSub}, + # Start the Endpoint (http/https) + HelloWorldWeb.Endpoint + # Start a worker by calling: HelloWorld.Worker.start_link(arg) + # {HelloWorld.Worker, arg} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: HelloWorld.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + HelloWorldWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/phoenix-1.6/hello_world/lib/hello_world/mailer.ex b/phoenix-1.6/hello_world/lib/hello_world/mailer.ex new file mode 100644 index 000000000..847f89ff3 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world/mailer.ex @@ -0,0 +1,3 @@ +defmodule HelloWorld.Mailer do + use Swoosh.Mailer, otp_app: :hello_world +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web.ex b/phoenix-1.6/hello_world/lib/hello_world_web.ex new file mode 100644 index 000000000..3f655ac3e --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web.ex @@ -0,0 +1,110 @@ +defmodule HelloWorldWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use HelloWorldWeb, :controller + use HelloWorldWeb, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define any helper function in modules + and import those modules here. + """ + + def controller do + quote do + use Phoenix.Controller, namespace: HelloWorldWeb + + import Plug.Conn + import HelloWorldWeb.Gettext + alias HelloWorldWeb.Router.Helpers, as: Routes + end + end + + def view do + quote do + use Phoenix.View, + root: "lib/hello_world_web/templates", + namespace: HelloWorldWeb + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] + + # Include shared imports and aliases for views + unquote(view_helpers()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {HelloWorldWeb.LayoutView, "live.html"} + + unquote(view_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(view_helpers()) + end + end + + def component do + quote do + use Phoenix.Component + + unquote(view_helpers()) + end + end + + def router do + quote do + use Phoenix.Router + + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + import HelloWorldWeb.Gettext + end + end + + defp view_helpers do + quote do + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) + import Phoenix.LiveView.Helpers + + # Import basic rendering functionality (render, render_layout, etc) + import Phoenix.View + + import HelloWorldWeb.ErrorHelpers + import HelloWorldWeb.Gettext + alias HelloWorldWeb.Router.Helpers, as: Routes + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/controllers/page_controller.ex b/phoenix-1.6/hello_world/lib/hello_world_web/controllers/page_controller.ex new file mode 100644 index 000000000..d4001c9e7 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/controllers/page_controller.ex @@ -0,0 +1,7 @@ +defmodule HelloWorldWeb.PageController do + use HelloWorldWeb, :controller + + def index(conn, _params) do + render(conn, "index.html") + end +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/endpoint.ex b/phoenix-1.6/hello_world/lib/hello_world_web/endpoint.ex new file mode 100644 index 000000000..5d6356180 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/endpoint.ex @@ -0,0 +1,49 @@ +defmodule HelloWorldWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :hello_world + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_hello_world_key", + signing_salt: "XvoVic4F" + ] + + socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phx.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", + from: :hello_world, + gzip: false, + only: ~w(assets fonts images favicon.ico robots.txt) + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug HelloWorldWeb.Router +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/gettext.ex b/phoenix-1.6/hello_world/lib/hello_world_web/gettext.ex new file mode 100644 index 000000000..9c0556fed --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule HelloWorldWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import HelloWorldWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :hello_world +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/router.ex b/phoenix-1.6/hello_world/lib/hello_world_web/router.ex new file mode 100644 index 000000000..aae690e82 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/router.ex @@ -0,0 +1,56 @@ +defmodule HelloWorldWeb.Router do + use HelloWorldWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, {HelloWorldWeb.LayoutView, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", HelloWorldWeb do + pipe_through :browser + + get "/", PageController, :index + end + + # Other scopes may use custom stacks. + # scope "/api", HelloWorldWeb do + # pipe_through :api + # end + + # Enables LiveDashboard only for development + # + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + if Mix.env() in [:dev, :test] do + import Phoenix.LiveDashboard.Router + + scope "/" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: HelloWorldWeb.Telemetry + end + end + + # Enables the Swoosh mailbox preview in development. + # + # Note that preview only shows emails that were sent by the same + # node running the Phoenix server. + if Mix.env() == :dev do + scope "/dev" do + pipe_through :browser + + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/telemetry.ex b/phoenix-1.6/hello_world/lib/hello_world_web/telemetry.ex new file mode 100644 index 000000000..bce99d5b2 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/telemetry.ex @@ -0,0 +1,48 @@ +defmodule HelloWorldWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {HelloWorldWeb, :count_users, []} + ] + end +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/templates/layout/app.html.heex b/phoenix-1.6/hello_world/lib/hello_world_web/templates/layout/app.html.heex new file mode 100644 index 000000000..169aed956 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/templates/layout/app.html.heex @@ -0,0 +1,5 @@ +
+ + + <%= @inner_content %> +
diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/templates/layout/live.html.heex b/phoenix-1.6/hello_world/lib/hello_world_web/templates/layout/live.html.heex new file mode 100644 index 000000000..a29d60448 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/templates/layout/live.html.heex @@ -0,0 +1,11 @@ +
+ + + + + <%= @inner_content %> +
diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/templates/layout/root.html.heex b/phoenix-1.6/hello_world/lib/hello_world_web/templates/layout/root.html.heex new file mode 100644 index 000000000..ddf1c1a18 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/templates/layout/root.html.heex @@ -0,0 +1,30 @@ + + + + + + + + <%= live_title_tag assigns[:page_title] || "HelloWorld", suffix: " Ā· Phoenix Framework" %> + + + + +
+
+ + +
+
+ <%= @inner_content %> + + diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/templates/page/index.html.heex b/phoenix-1.6/hello_world/lib/hello_world_web/templates/page/index.html.heex new file mode 100644 index 000000000..7582aee5e --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/templates/page/index.html.heex @@ -0,0 +1,41 @@ +
+

Hello World!

+

Melting minds from prototype to production

+
+ +
+
+

Resources

+ +
+
+

Help

+ +
+
diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/views/error_helpers.ex b/phoenix-1.6/hello_world/lib/hello_world_web/views/error_helpers.ex new file mode 100644 index 000000000..89f8cf016 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/views/error_helpers.ex @@ -0,0 +1,47 @@ +defmodule HelloWorldWeb.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + use Phoenix.HTML + + @doc """ + Generates tag for inlined form input errors. + """ + def error_tag(form, field) do + Enum.map(Keyword.get_values(form.errors, field), fn error -> + content_tag(:span, translate_error(error), + class: "invalid-feedback", + phx_feedback_for: input_name(form, field) + ) + end) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate "is invalid" in the "errors" domain + # dgettext("errors", "is invalid") + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + # This requires us to call the Gettext module passing our gettext + # backend as first argument. + # + # Note we use the "errors" domain, which means translations + # should be written to the errors.po file. The :count option is + # set by Ecto and indicates we should also apply plural rules. + if count = opts[:count] do + Gettext.dngettext(HelloWorldWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(HelloWorldWeb.Gettext, "errors", msg, opts) + end + end +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/views/error_view.ex b/phoenix-1.6/hello_world/lib/hello_world_web/views/error_view.ex new file mode 100644 index 000000000..e2314a13f --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/views/error_view.ex @@ -0,0 +1,16 @@ +defmodule HelloWorldWeb.ErrorView do + use HelloWorldWeb, :view + + # If you want to customize a particular status code + # for a certain format, you may uncomment below. + # def render("500.html", _assigns) do + # "Internal Server Error" + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.html" becomes + # "Not Found". + def template_not_found(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/views/layout_view.ex b/phoenix-1.6/hello_world/lib/hello_world_web/views/layout_view.ex new file mode 100644 index 000000000..09a7cd49a --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/views/layout_view.ex @@ -0,0 +1,7 @@ +defmodule HelloWorldWeb.LayoutView do + use HelloWorldWeb, :view + + # Phoenix LiveDashboard is available only in development by default, + # so we instruct Elixir to not warn if the dashboard route is missing. + @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}} +end diff --git a/phoenix-1.6/hello_world/lib/hello_world_web/views/page_view.ex b/phoenix-1.6/hello_world/lib/hello_world_web/views/page_view.ex new file mode 100644 index 000000000..ad0f45457 --- /dev/null +++ b/phoenix-1.6/hello_world/lib/hello_world_web/views/page_view.ex @@ -0,0 +1,3 @@ +defmodule HelloWorldWeb.PageView do + use HelloWorldWeb, :view +end diff --git a/phoenix-1.6/hello_world/mix.exs b/phoenix-1.6/hello_world/mix.exs new file mode 100644 index 000000000..c569936c7 --- /dev/null +++ b/phoenix-1.6/hello_world/mix.exs @@ -0,0 +1,64 @@ +defmodule HelloWorld.MixProject do + use Mix.Project + + def project do + [ + app: :hello_world, + version: "0.1.0", + elixir: "~> 1.12", + elixirc_paths: elixirc_paths(Mix.env()), + # compilers: [:gettext] ++ Mix.compilers(), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {HelloWorld.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.6.15"}, + {:phoenix_html, "~> 3.0"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 0.17.5"}, + {:floki, ">= 0.30.0", only: :test}, + {:phoenix_live_dashboard, "~> 0.6"}, + {:esbuild, "~> 0.4", runtime: Mix.env() == :dev}, + {:swoosh, "~> 1.3"}, + {:telemetry_metrics, "~> 0.6"}, + {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 0.18"}, + {:jason, "~> 1.2"}, + {:plug_cowboy, "~> 2.5"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to install project dependencies and perform other setup tasks, run: + # + # $ mix setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get"], + "assets.deploy": ["esbuild default --minify", "phx.digest"] + ] + end +end diff --git a/phoenix-1.6/hello_world/mix.lock b/phoenix-1.6/hello_world/mix.lock new file mode 100644 index 000000000..47b126398 --- /dev/null +++ b/phoenix-1.6/hello_world/mix.lock @@ -0,0 +1,29 @@ +%{ + "castore": {:hex, :castore, "1.0.1", "240b9edb4e9e94f8f56ab39d8d2d0a57f49e46c56aced8f873892df8ff64ff5a", [:mix], [], "hexpm", "b4951de93c224d44fac71614beabd88b71932d0b1dea80d2f80fb9044e01bbb3"}, + "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, + "esbuild": {:hex, :esbuild, "0.6.1", "a774bfa7b4512a1211bf15880b462be12a4c48ed753a170c68c63b2c95888150", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "569f7409fb5a932211573fc20e2a930a0d5cf3377c5b4f6506c651b1783a1678"}, + "expo": {:hex, :expo, "0.4.0", "bbe4bf455e2eb2ebd2f1e7d83530ce50fb9990eb88fc47855c515bfdf1c6626f", [:mix], [], "hexpm", "a8ed1683ec8b7c7fa53fd7a41b2c6935f539168a6bb0616d7fd6b58a36f3abf2"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"}, + "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, + "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.14", "5ec615d4d61bf9d4755f158bd6c80372b715533fe6d6219e12d74fb5eedbeac1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "afeb6ba43ce329a6f7fc1c9acdfc6d3039995345f025febb7f409a92f6faebd3"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, + "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, +} diff --git a/phoenix-1.6/hello_world/priv/gettext/en/LC_MESSAGES/errors.po b/phoenix-1.6/hello_world/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 000000000..cdec3a113 --- /dev/null +++ b/phoenix-1.6/hello_world/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,11 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" diff --git a/phoenix-1.6/hello_world/priv/gettext/errors.pot b/phoenix-1.6/hello_world/priv/gettext/errors.pot new file mode 100644 index 000000000..d6f47fa87 --- /dev/null +++ b/phoenix-1.6/hello_world/priv/gettext/errors.pot @@ -0,0 +1,10 @@ +## This is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here has no +## effect: edit them in PO (`.po`) files instead. + diff --git a/phoenix-1.6/hello_world/priv/static/favicon.ico b/phoenix-1.6/hello_world/priv/static/favicon.ico new file mode 100644 index 000000000..73de524aa Binary files /dev/null and b/phoenix-1.6/hello_world/priv/static/favicon.ico differ diff --git a/phoenix-1.6/hello_world/priv/static/images/phoenix.png b/phoenix-1.6/hello_world/priv/static/images/phoenix.png new file mode 100644 index 000000000..9c81075f6 Binary files /dev/null and b/phoenix-1.6/hello_world/priv/static/images/phoenix.png differ diff --git a/phoenix-1.6/hello_world/priv/static/robots.txt b/phoenix-1.6/hello_world/priv/static/robots.txt new file mode 100644 index 000000000..26e06b5f1 --- /dev/null +++ b/phoenix-1.6/hello_world/priv/static/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/phoenix-1.6/hello_world/test/hello_world_web/controllers/page_controller_test.exs b/phoenix-1.6/hello_world/test/hello_world_web/controllers/page_controller_test.exs new file mode 100644 index 000000000..6151e3a7b --- /dev/null +++ b/phoenix-1.6/hello_world/test/hello_world_web/controllers/page_controller_test.exs @@ -0,0 +1,8 @@ +defmodule HelloWorldWeb.PageControllerTest do + use HelloWorldWeb.ConnCase + + test "GET /", %{conn: conn} do + conn = get(conn, "/") + assert html_response(conn, 200) =~ "Welcome to Phoenix!" + end +end diff --git a/phoenix-1.6/hello_world/test/hello_world_web/views/error_view_test.exs b/phoenix-1.6/hello_world/test/hello_world_web/views/error_view_test.exs new file mode 100644 index 000000000..bfad82af6 --- /dev/null +++ b/phoenix-1.6/hello_world/test/hello_world_web/views/error_view_test.exs @@ -0,0 +1,14 @@ +defmodule HelloWorldWeb.ErrorViewTest do + use HelloWorldWeb.ConnCase, async: true + + # Bring render/3 and render_to_string/3 for testing custom views + import Phoenix.View + + test "renders 404.html" do + assert render_to_string(HelloWorldWeb.ErrorView, "404.html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(HelloWorldWeb.ErrorView, "500.html", []) == "Internal Server Error" + end +end diff --git a/phoenix-1.6/hello_world/test/hello_world_web/views/layout_view_test.exs b/phoenix-1.6/hello_world/test/hello_world_web/views/layout_view_test.exs new file mode 100644 index 000000000..7ded59b49 --- /dev/null +++ b/phoenix-1.6/hello_world/test/hello_world_web/views/layout_view_test.exs @@ -0,0 +1,8 @@ +defmodule HelloWorldWeb.LayoutViewTest do + use HelloWorldWeb.ConnCase, async: true + + # When testing helpers, you may want to import Phoenix.HTML and + # use functions such as safe_to_string() to convert the helper + # result into an HTML string. + # import Phoenix.HTML +end diff --git a/phoenix-1.6/hello_world/test/hello_world_web/views/page_view_test.exs b/phoenix-1.6/hello_world/test/hello_world_web/views/page_view_test.exs new file mode 100644 index 000000000..2af2c100b --- /dev/null +++ b/phoenix-1.6/hello_world/test/hello_world_web/views/page_view_test.exs @@ -0,0 +1,3 @@ +defmodule HelloWorldWeb.PageViewTest do + use HelloWorldWeb.ConnCase, async: true +end diff --git a/phoenix-1.6/hello_world/test/support/conn_case.ex b/phoenix-1.6/hello_world/test/support/conn_case.ex new file mode 100644 index 000000000..7244281c1 --- /dev/null +++ b/phoenix-1.6/hello_world/test/support/conn_case.ex @@ -0,0 +1,37 @@ +defmodule HelloWorldWeb.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use HelloWorldWeb.ConnCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import HelloWorldWeb.ConnCase + + alias HelloWorldWeb.Router.Helpers, as: Routes + + # The default endpoint for testing + @endpoint HelloWorldWeb.Endpoint + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/phoenix-1.6/hello_world/test/test_helper.exs b/phoenix-1.6/hello_world/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/phoenix-1.6/hello_world/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/phoenix-1.6/navigation/.formatter.exs b/phoenix-1.6/navigation/.formatter.exs new file mode 100644 index 000000000..47616780b --- /dev/null +++ b/phoenix-1.6/navigation/.formatter.exs @@ -0,0 +1,4 @@ +[ + import_deps: [:phoenix], + inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/phoenix-1.6/navigation/.gitignore b/phoenix-1.6/navigation/.gitignore new file mode 100644 index 000000000..7e90cfa49 --- /dev/null +++ b/phoenix-1.6/navigation/.gitignore @@ -0,0 +1,34 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +navigation-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + diff --git a/phoenix-1.6/navigation/README.md b/phoenix-1.6/navigation/README.md new file mode 100644 index 000000000..bb774abbb --- /dev/null +++ b/phoenix-1.6/navigation/README.md @@ -0,0 +1,18 @@ +# Navigation + +To start your Phoenix server: + + * Install dependencies with `mix deps.get` + * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + + * Official website: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/phoenix-1.6/navigation/assets/css/app.css b/phoenix-1.6/navigation/assets/css/app.css new file mode 100644 index 000000000..19c2e51ed --- /dev/null +++ b/phoenix-1.6/navigation/assets/css/app.css @@ -0,0 +1,120 @@ +/* This file is for your main application CSS */ +@import "./phoenix.css"; + +/* Alerts and form errors used by phx.new */ +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert p { + margin-bottom: 0; +} +.alert:empty { + display: none; +} +.invalid-feedback { + color: #a94442; + display: block; + margin: -1rem 0 2rem; +} + +/* LiveView specific classes for your customization */ +.phx-no-feedback.invalid-feedback, +.phx-no-feedback .invalid-feedback { + display: none; +} + +.phx-click-loading { + opacity: 0.5; + transition: opacity 1s ease-out; +} + +.phx-loading{ + cursor: wait; +} + +.phx-modal { + opacity: 1!important; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.4); +} + +.phx-modal-content { + background-color: #fefefe; + margin: 15vh auto; + padding: 20px; + border: 1px solid #888; + width: 80%; +} + +.phx-modal-close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; +} + +.phx-modal-close:hover, +.phx-modal-close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + +.fade-in-scale { + animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys; +} + +.fade-out-scale { + animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys; +} + +.fade-in { + animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys; +} +.fade-out { + animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys; +} + +@keyframes fade-in-scale-keys{ + 0% { scale: 0.95; opacity: 0; } + 100% { scale: 1.0; opacity: 1; } +} + +@keyframes fade-out-scale-keys{ + 0% { scale: 1.0; opacity: 1; } + 100% { scale: 0.95; opacity: 0; } +} + +@keyframes fade-in-keys{ + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +@keyframes fade-out-keys{ + 0% { opacity: 1; } + 100% { opacity: 0; } +} diff --git a/phoenix-1.6/navigation/assets/css/phoenix.css b/phoenix-1.6/navigation/assets/css/phoenix.css new file mode 100644 index 000000000..0d59050f8 --- /dev/null +++ b/phoenix-1.6/navigation/assets/css/phoenix.css @@ -0,0 +1,101 @@ +/* Includes some default style for the starter application. + * This can be safely deleted to start fresh. + */ + +/* Milligram v1.4.1 https://milligram.github.io + * Copyright (c) 2020 CJ Patoilo Licensed under the MIT license + */ + +*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#000000;font-family:'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#0069d9;border:0.1rem solid #0069d9;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#0069d9;border-color:#0069d9}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#0069d9}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#0069d9}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#0069d9}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#0069d9}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #0069d9;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='color'],input[type='date'],input[type='datetime'],input[type='datetime-local'],input[type='email'],input[type='month'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],input[type='week'],input:not([type]),textarea,select{-webkit-appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem .7rem;width:100%}input[type='color']:focus,input[type='date']:focus,input[type='datetime']:focus,input[type='datetime-local']:focus,input[type='email']:focus,input[type='month']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,input[type='week']:focus,input:not([type]):focus,textarea:focus,select:focus{border-color:#0069d9;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}select[multiple]{background:none;height:auto}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-40{margin-left:40%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-60{margin-left:60%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#0069d9;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;display:block;overflow-x:auto;text-align:left;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}@media (min-width: 40rem){table{display:table;overflow-x:initial}}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right} + +/* General style */ +h1{font-size: 3.6rem; line-height: 1.25} +h2{font-size: 2.8rem; line-height: 1.3} +h3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35} +h4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5} +h5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4} +h6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2} +pre{padding: 1em;} + +.container{ + margin: 0 auto; + max-width: 80.0rem; + padding: 0 2.0rem; + position: relative; + width: 100% +} +select { + width: auto; +} + +/* Phoenix promo and logo */ +.phx-hero { + text-align: center; + border-bottom: 1px solid #e3e3e3; + background: #eee; + border-radius: 6px; + padding: 3em 3em 1em; + margin-bottom: 3rem; + font-weight: 200; + font-size: 120%; +} +.phx-hero input { + background: #ffffff; +} +.phx-logo { + min-width: 300px; + margin: 1rem; + display: block; +} +.phx-logo img { + width: auto; + display: block; +} + +/* Headers */ +header { + width: 100%; + background: #fdfdfd; + border-bottom: 1px solid #eaeaea; + margin-bottom: 2rem; +} +header section { + align-items: center; + display: flex; + flex-direction: column; + justify-content: space-between; +} +header section :first-child { + order: 2; +} +header section :last-child { + order: 1; +} +header nav ul, +header nav li { + margin: 0; + padding: 0; + display: block; + text-align: right; + white-space: nowrap; +} +header nav ul { + margin: 1rem; + margin-top: 0; +} +header nav a { + display: block; +} + +@media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */ + header section { + flex-direction: row; + } + header nav ul { + margin: 1rem; + } + .phx-logo { + flex-basis: 527px; + margin: 2rem 1rem; + } +} diff --git a/phoenix-1.6/navigation/assets/js/app.js b/phoenix-1.6/navigation/assets/js/app.js new file mode 100644 index 000000000..2ca06a566 --- /dev/null +++ b/phoenix-1.6/navigation/assets/js/app.js @@ -0,0 +1,45 @@ +// We import the CSS which is extracted to its own file by esbuild. +// Remove this line if you add a your own CSS build pipeline (e.g postcss). +import "../css/app.css" + +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import topbar from "../vendor/topbar" + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", info => topbar.show()) +window.addEventListener("phx:page-loading-stop", info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + diff --git a/phoenix-1.6/navigation/assets/vendor/topbar.js b/phoenix-1.6/navigation/assets/vendor/topbar.js new file mode 100644 index 000000000..1f6220974 --- /dev/null +++ b/phoenix-1.6/navigation/assets/vendor/topbar.js @@ -0,0 +1,157 @@ +/** + * @license MIT + * topbar 1.0.0, 2021-01-06 + * https://buunguyen.github.io/topbar + * Copyright (c) 2021 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + // https://gist.github.com/paulirish/1579671 + (function () { + var lastTime = 0; + var vendors = ["ms", "moz", "webkit", "o"]; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x] + "RequestAnimationFrame"]; + window.cancelAnimationFrame = + window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"]; + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + })(); + + var canvas, + progressTimerId, + fadeTimerId, + currentProgress, + showing, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + document.body.appendChild(canvas); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function () { + if (showing) return; + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/phoenix-1.6/navigation/config/config.exs b/phoenix-1.6/navigation/config/config.exs new file mode 100644 index 000000000..a1fbfdef3 --- /dev/null +++ b/phoenix-1.6/navigation/config/config.exs @@ -0,0 +1,49 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +# Configures the endpoint +config :navigation, NavigationWeb.Endpoint, + url: [host: "localhost"], + render_errors: [view: NavigationWeb.ErrorView, accepts: ~w(html json), layout: false], + pubsub_server: Navigation.PubSub, + live_view: [signing_salt: "Z2PkgCoo"] + +# Configures the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :navigation, Navigation.Mailer, adapter: Swoosh.Adapters.Local + +# Swoosh API client is needed for adapters other than SMTP. +config :swoosh, :api_client, false + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.14.29", + default: [ + args: + ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/phoenix-1.6/navigation/config/dev.exs b/phoenix-1.6/navigation/config/dev.exs new file mode 100644 index 000000000..b7e24d5a0 --- /dev/null +++ b/phoenix-1.6/navigation/config/dev.exs @@ -0,0 +1,65 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we use it +# with esbuild to bundle .js and .css sources. +config :navigation, NavigationWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "+4jQCsy5y/0czKzQhRf7uW+RBNXHgpDqe0z5RBTjT+tpKP/EfxeKlw4ckhXccxVt", + watchers: [ + # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args) + esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Note that this task requires Erlang/OTP 20 or later. +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :navigation, NavigationWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/navigation_web/(live|views)/.*(ex)$", + ~r"lib/navigation_web/templates/.*(eex)$" + ] + ] + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/phoenix-1.6/navigation/config/prod.exs b/phoenix-1.6/navigation/config/prod.exs new file mode 100644 index 000000000..ffbc34948 --- /dev/null +++ b/phoenix-1.6/navigation/config/prod.exs @@ -0,0 +1,49 @@ +import Config + +# For production, don't forget to configure the url host +# to something meaningful, Phoenix uses this information +# when generating URLs. +# +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix phx.digest` task, +# which you should run after static files are built and +# before starting your production server. +config :navigation, NavigationWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" + +# Do not print debug messages in production +config :logger, level: :info + +# ## SSL Support +# +# To get SSL working, you will need to add the `https` key +# to the previous section and set your `:url` port to 443: +# +# config :navigation, NavigationWeb.Endpoint, +# ..., +# url: [host: "example.com", port: 443], +# https: [ +# ..., +# port: 443, +# cipher_suite: :strong, +# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), +# certfile: System.get_env("SOME_APP_SSL_CERT_PATH") +# ] +# +# The `cipher_suite` is set to `:strong` to support only the +# latest and more secure SSL ciphers. This means old browsers +# and clients may not be supported. You can set it to +# `:compatible` for wider support. +# +# `:keyfile` and `:certfile` expect an absolute path to the key +# and cert in disk or a relative path inside priv, for example +# "priv/ssl/server.key". For all supported SSL configuration +# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 +# +# We also recommend setting `force_ssl` in your endpoint, ensuring +# no data is ever sent via http, always redirecting to https: +# +# config :navigation, NavigationWeb.Endpoint, +# force_ssl: [hsts: true] +# +# Check `Plug.SSL` for all available options in `force_ssl`. diff --git a/phoenix-1.6/navigation/config/runtime.exs b/phoenix-1.6/navigation/config/runtime.exs new file mode 100644 index 000000000..80cbb1ac4 --- /dev/null +++ b/phoenix-1.6/navigation/config/runtime.exs @@ -0,0 +1,68 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/navigation start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :navigation, NavigationWeb.Endpoint, server: true +end + +if config_env() == :prod do + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + port = String.to_integer(System.get_env("PORT") || "4000") + + config :navigation, NavigationWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base + + # ## Configuring the mailer + # + # In production you need to configure the mailer to use a different adapter. + # Also, you may need to configure the Swoosh API client of your choice if you + # are not using SMTP. Here is an example of the configuration: + # + # config :navigation, Navigation.Mailer, + # adapter: Swoosh.Adapters.Mailgun, + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("MAILGUN_DOMAIN") + # + # For this example you need include a HTTP client required by Swoosh API client. + # Swoosh supports Hackney and Finch out of the box: + # + # config :swoosh, :api_client, Swoosh.ApiClient.Hackney + # + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. +end diff --git a/phoenix-1.6/navigation/config/test.exs b/phoenix-1.6/navigation/config/test.exs new file mode 100644 index 000000000..36ed75023 --- /dev/null +++ b/phoenix-1.6/navigation/config/test.exs @@ -0,0 +1,18 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :navigation, NavigationWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "Cz8ERwLtloX4VXl5agl0hv3JFCG3HOiodGhg9MarTf6IPXOuGT42tC+S2hltZojZ", + server: false + +# In test we don't send emails. +config :navigation, Navigation.Mailer, + adapter: Swoosh.Adapters.Test + +# Print only warnings and errors during test +config :logger, level: :warn + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/phoenix-1.6/navigation/lib/navigation.ex b/phoenix-1.6/navigation/lib/navigation.ex new file mode 100644 index 000000000..e2a385585 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation.ex @@ -0,0 +1,9 @@ +defmodule Navigation do + @moduledoc """ + Navigation keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/phoenix-1.6/navigation/lib/navigation/application.ex b/phoenix-1.6/navigation/lib/navigation/application.ex new file mode 100644 index 000000000..6f9c51bc5 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation/application.ex @@ -0,0 +1,34 @@ +defmodule Navigation.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + # Start the Telemetry supervisor + NavigationWeb.Telemetry, + # Start the PubSub system + {Phoenix.PubSub, name: Navigation.PubSub}, + # Start the Endpoint (http/https) + NavigationWeb.Endpoint + # Start a worker by calling: Navigation.Worker.start_link(arg) + # {Navigation.Worker, arg} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Navigation.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + NavigationWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/phoenix-1.6/navigation/lib/navigation/mailer.ex b/phoenix-1.6/navigation/lib/navigation/mailer.ex new file mode 100644 index 000000000..2ae9e757e --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation/mailer.ex @@ -0,0 +1,3 @@ +defmodule Navigation.Mailer do + use Swoosh.Mailer, otp_app: :navigation +end diff --git a/phoenix-1.6/navigation/lib/navigation_web.ex b/phoenix-1.6/navigation/lib/navigation_web.ex new file mode 100644 index 000000000..8b0a3a9ee --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web.ex @@ -0,0 +1,110 @@ +defmodule NavigationWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use NavigationWeb, :controller + use NavigationWeb, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define any helper function in modules + and import those modules here. + """ + + def controller do + quote do + use Phoenix.Controller, namespace: NavigationWeb + + import Plug.Conn + import NavigationWeb.Gettext + alias NavigationWeb.Router.Helpers, as: Routes + end + end + + def view do + quote do + use Phoenix.View, + root: "lib/navigation_web/templates", + namespace: NavigationWeb + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] + + # Include shared imports and aliases for views + unquote(view_helpers()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {NavigationWeb.LayoutView, "live.html"} + + unquote(view_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(view_helpers()) + end + end + + def component do + quote do + use Phoenix.Component + + unquote(view_helpers()) + end + end + + def router do + quote do + use Phoenix.Router + + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + import NavigationWeb.Gettext + end + end + + defp view_helpers do + quote do + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) + import Phoenix.LiveView.Helpers + + # Import basic rendering functionality (render, render_layout, etc) + import Phoenix.View + + import NavigationWeb.ErrorHelpers + import NavigationWeb.Gettext + alias NavigationWeb.Router.Helpers, as: Routes + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/controllers/about_controller.ex b/phoenix-1.6/navigation/lib/navigation_web/controllers/about_controller.ex new file mode 100644 index 000000000..ed96b247d --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/controllers/about_controller.ex @@ -0,0 +1,7 @@ +defmodule NavigationWeb.AboutController do + use NavigationWeb, :controller + + def about(conn, _params) do + render(conn, "about.html") + end +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/controllers/home_controller.ex b/phoenix-1.6/navigation/lib/navigation_web/controllers/home_controller.ex new file mode 100644 index 000000000..be52a558d --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/controllers/home_controller.ex @@ -0,0 +1,7 @@ +defmodule NavigationWeb.HomeController do + use NavigationWeb, :controller + + def home(conn, _params) do + render(conn, "home.html") + end +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/controllers/page_controller.ex b/phoenix-1.6/navigation/lib/navigation_web/controllers/page_controller.ex new file mode 100644 index 000000000..362a45f16 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/controllers/page_controller.ex @@ -0,0 +1,7 @@ +defmodule NavigationWeb.PageController do + use NavigationWeb, :controller + + def index(conn, _params) do + render(conn, "index.html") + end +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/controllers/projects_controller.ex b/phoenix-1.6/navigation/lib/navigation_web/controllers/projects_controller.ex new file mode 100644 index 000000000..c7c3d2d3d --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/controllers/projects_controller.ex @@ -0,0 +1,7 @@ +defmodule NavigationWeb.ProjectsController do + use NavigationWeb, :controller + + def projects(conn, _params) do + render(conn, "projects.html") + end +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/endpoint.ex b/phoenix-1.6/navigation/lib/navigation_web/endpoint.ex new file mode 100644 index 000000000..e2416c0b5 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/endpoint.ex @@ -0,0 +1,49 @@ +defmodule NavigationWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :navigation + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_navigation_key", + signing_salt: "0XYo/f0H" + ] + + socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phx.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", + from: :navigation, + gzip: false, + only: ~w(assets fonts images favicon.ico robots.txt) + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug NavigationWeb.Router +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/gettext.ex b/phoenix-1.6/navigation/lib/navigation_web/gettext.ex new file mode 100644 index 000000000..7cca2250f --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule NavigationWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import NavigationWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :navigation +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/router.ex b/phoenix-1.6/navigation/lib/navigation_web/router.ex new file mode 100644 index 000000000..221453535 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/router.ex @@ -0,0 +1,59 @@ +defmodule NavigationWeb.Router do + use NavigationWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, {NavigationWeb.LayoutView, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", NavigationWeb do + pipe_through :browser + + get "/", PageController, :index + get "/home", HomeController, :home + get "/about", AboutController, :about + get "/projects", ProjectsController, :projects + end + + # Other scopes may use custom stacks. + # scope "/api", NavigationWeb do + # pipe_through :api + # end + + # Enables LiveDashboard only for development + # + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + if Mix.env() in [:dev, :test] do + import Phoenix.LiveDashboard.Router + + scope "/" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: NavigationWeb.Telemetry + end + end + + # Enables the Swoosh mailbox preview in development. + # + # Note that preview only shows emails that were sent by the same + # node running the Phoenix server. + if Mix.env() == :dev do + scope "/dev" do + pipe_through :browser + + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/telemetry.ex b/phoenix-1.6/navigation/lib/navigation_web/telemetry.ex new file mode 100644 index 000000000..be9fe9bc7 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/telemetry.ex @@ -0,0 +1,48 @@ +defmodule NavigationWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {NavigationWeb, :count_users, []} + ] + end +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/templates/about/about.html.heex b/phoenix-1.6/navigation/lib/navigation_web/templates/about/about.html.heex new file mode 100644 index 000000000..8eb36c1f5 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/templates/about/about.html.heex @@ -0,0 +1 @@ +

About Page

diff --git a/phoenix-1.6/navigation/lib/navigation_web/templates/home/home.html.heex b/phoenix-1.6/navigation/lib/navigation_web/templates/home/home.html.heex new file mode 100644 index 000000000..d62a4c404 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/templates/home/home.html.heex @@ -0,0 +1 @@ +

Home Page

diff --git a/phoenix-1.6/navigation/lib/navigation_web/templates/layout/app.html.heex b/phoenix-1.6/navigation/lib/navigation_web/templates/layout/app.html.heex new file mode 100644 index 000000000..169aed956 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/templates/layout/app.html.heex @@ -0,0 +1,5 @@ +
+ + + <%= @inner_content %> +
diff --git a/phoenix-1.6/navigation/lib/navigation_web/templates/layout/live.html.heex b/phoenix-1.6/navigation/lib/navigation_web/templates/layout/live.html.heex new file mode 100644 index 000000000..a29d60448 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/templates/layout/live.html.heex @@ -0,0 +1,11 @@ +
+ + + + + <%= @inner_content %> +
diff --git a/phoenix-1.6/navigation/lib/navigation_web/templates/layout/root.html.heex b/phoenix-1.6/navigation/lib/navigation_web/templates/layout/root.html.heex new file mode 100644 index 000000000..c72fc27e4 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/templates/layout/root.html.heex @@ -0,0 +1,39 @@ + + + + + + + + <%= live_title_tag assigns[:page_title] || "Navigation", suffix: " Ā· Phoenix Framework" %> + + + + + +
+
+ + +
+
+ <%= @inner_content %> + + + diff --git a/phoenix-1.6/navigation/lib/navigation_web/templates/page/home/home.html.heex b/phoenix-1.6/navigation/lib/navigation_web/templates/page/home/home.html.heex new file mode 100644 index 000000000..d62a4c404 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/templates/page/home/home.html.heex @@ -0,0 +1 @@ +

Home Page

diff --git a/phoenix-1.6/navigation/lib/navigation_web/templates/page/index.html.heex b/phoenix-1.6/navigation/lib/navigation_web/templates/page/index.html.heex new file mode 100644 index 000000000..f844bd8d7 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/templates/page/index.html.heex @@ -0,0 +1,41 @@ +
+

<%= gettext "Welcome to %{name}!", name: "Phoenix" %>

+

Peace of mind from prototype to production

+
+ +
+
+

Resources

+ +
+
+

Help

+ +
+
diff --git a/phoenix-1.6/navigation/lib/navigation_web/templates/page/projects/projects.html.heex b/phoenix-1.6/navigation/lib/navigation_web/templates/page/projects/projects.html.heex new file mode 100644 index 000000000..f1b92b1cc --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/templates/page/projects/projects.html.heex @@ -0,0 +1 @@ +

Projects Page

diff --git a/phoenix-1.6/navigation/lib/navigation_web/templates/projects/projects.html.heex b/phoenix-1.6/navigation/lib/navigation_web/templates/projects/projects.html.heex new file mode 100644 index 000000000..f1b92b1cc --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/templates/projects/projects.html.heex @@ -0,0 +1 @@ +

Projects Page

diff --git a/phoenix-1.6/navigation/lib/navigation_web/views/about_view.ex b/phoenix-1.6/navigation/lib/navigation_web/views/about_view.ex new file mode 100644 index 000000000..7c120b2e3 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/views/about_view.ex @@ -0,0 +1,3 @@ +defmodule NavigationWeb.AboutView do + use NavigationWeb, :view +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/views/error_helpers.ex b/phoenix-1.6/navigation/lib/navigation_web/views/error_helpers.ex new file mode 100644 index 000000000..c772d7604 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/views/error_helpers.ex @@ -0,0 +1,47 @@ +defmodule NavigationWeb.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + use Phoenix.HTML + + @doc """ + Generates tag for inlined form input errors. + """ + def error_tag(form, field) do + Enum.map(Keyword.get_values(form.errors, field), fn error -> + content_tag(:span, translate_error(error), + class: "invalid-feedback", + phx_feedback_for: input_name(form, field) + ) + end) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate "is invalid" in the "errors" domain + # dgettext("errors", "is invalid") + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + # This requires us to call the Gettext module passing our gettext + # backend as first argument. + # + # Note we use the "errors" domain, which means translations + # should be written to the errors.po file. The :count option is + # set by Ecto and indicates we should also apply plural rules. + if count = opts[:count] do + Gettext.dngettext(NavigationWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(NavigationWeb.Gettext, "errors", msg, opts) + end + end +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/views/error_view.ex b/phoenix-1.6/navigation/lib/navigation_web/views/error_view.ex new file mode 100644 index 000000000..ec221081a --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/views/error_view.ex @@ -0,0 +1,16 @@ +defmodule NavigationWeb.ErrorView do + use NavigationWeb, :view + + # If you want to customize a particular status code + # for a certain format, you may uncomment below. + # def render("500.html", _assigns) do + # "Internal Server Error" + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.html" becomes + # "Not Found". + def template_not_found(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/views/home_view.ex b/phoenix-1.6/navigation/lib/navigation_web/views/home_view.ex new file mode 100644 index 000000000..af1f1f9d8 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/views/home_view.ex @@ -0,0 +1,3 @@ +defmodule NavigationWeb.HomeView do + use NavigationWeb, :view +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/views/layout_view.ex b/phoenix-1.6/navigation/lib/navigation_web/views/layout_view.ex new file mode 100644 index 000000000..397365e1f --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/views/layout_view.ex @@ -0,0 +1,7 @@ +defmodule NavigationWeb.LayoutView do + use NavigationWeb, :view + + # Phoenix LiveDashboard is available only in development by default, + # so we instruct Elixir to not warn if the dashboard route is missing. + @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}} +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/views/page_view.ex b/phoenix-1.6/navigation/lib/navigation_web/views/page_view.ex new file mode 100644 index 000000000..e45509231 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/views/page_view.ex @@ -0,0 +1,3 @@ +defmodule NavigationWeb.PageView do + use NavigationWeb, :view +end diff --git a/phoenix-1.6/navigation/lib/navigation_web/views/projects_view.ex b/phoenix-1.6/navigation/lib/navigation_web/views/projects_view.ex new file mode 100644 index 000000000..0b14919b4 --- /dev/null +++ b/phoenix-1.6/navigation/lib/navigation_web/views/projects_view.ex @@ -0,0 +1,3 @@ +defmodule NavigationWeb.ProjectsView do + use NavigationWeb, :view +end diff --git a/phoenix-1.6/navigation/mix.exs b/phoenix-1.6/navigation/mix.exs new file mode 100644 index 000000000..57206205e --- /dev/null +++ b/phoenix-1.6/navigation/mix.exs @@ -0,0 +1,64 @@ +defmodule Navigation.MixProject do + use Mix.Project + + def project do + [ + app: :navigation, + version: "0.1.0", + elixir: "~> 1.12", + elixirc_paths: elixirc_paths(Mix.env()), + compilers: [:gettext] ++ Mix.compilers(), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {Navigation.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.6.15"}, + {:phoenix_html, "~> 3.0"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 0.17.5"}, + {:floki, ">= 0.30.0", only: :test}, + {:phoenix_live_dashboard, "~> 0.6"}, + {:esbuild, "~> 0.4", runtime: Mix.env() == :dev}, + {:swoosh, "~> 1.3"}, + {:telemetry_metrics, "~> 0.6"}, + {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 0.18"}, + {:jason, "~> 1.2"}, + {:plug_cowboy, "~> 2.5"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to install project dependencies and perform other setup tasks, run: + # + # $ mix setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get"], + "assets.deploy": ["esbuild default --minify", "phx.digest"] + ] + end +end diff --git a/phoenix-1.6/navigation/mix.lock b/phoenix-1.6/navigation/mix.lock new file mode 100644 index 000000000..47b126398 --- /dev/null +++ b/phoenix-1.6/navigation/mix.lock @@ -0,0 +1,29 @@ +%{ + "castore": {:hex, :castore, "1.0.1", "240b9edb4e9e94f8f56ab39d8d2d0a57f49e46c56aced8f873892df8ff64ff5a", [:mix], [], "hexpm", "b4951de93c224d44fac71614beabd88b71932d0b1dea80d2f80fb9044e01bbb3"}, + "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, + "esbuild": {:hex, :esbuild, "0.6.1", "a774bfa7b4512a1211bf15880b462be12a4c48ed753a170c68c63b2c95888150", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "569f7409fb5a932211573fc20e2a930a0d5cf3377c5b4f6506c651b1783a1678"}, + "expo": {:hex, :expo, "0.4.0", "bbe4bf455e2eb2ebd2f1e7d83530ce50fb9990eb88fc47855c515bfdf1c6626f", [:mix], [], "hexpm", "a8ed1683ec8b7c7fa53fd7a41b2c6935f539168a6bb0616d7fd6b58a36f3abf2"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"}, + "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, + "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.14", "5ec615d4d61bf9d4755f158bd6c80372b715533fe6d6219e12d74fb5eedbeac1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "afeb6ba43ce329a6f7fc1c9acdfc6d3039995345f025febb7f409a92f6faebd3"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, + "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, +} diff --git a/phoenix-1.6/navigation/priv/gettext/en/LC_MESSAGES/errors.po b/phoenix-1.6/navigation/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 000000000..cdec3a113 --- /dev/null +++ b/phoenix-1.6/navigation/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,11 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" diff --git a/phoenix-1.6/navigation/priv/gettext/errors.pot b/phoenix-1.6/navigation/priv/gettext/errors.pot new file mode 100644 index 000000000..d6f47fa87 --- /dev/null +++ b/phoenix-1.6/navigation/priv/gettext/errors.pot @@ -0,0 +1,10 @@ +## This is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here has no +## effect: edit them in PO (`.po`) files instead. + diff --git a/phoenix-1.6/navigation/priv/static/favicon.ico b/phoenix-1.6/navigation/priv/static/favicon.ico new file mode 100644 index 000000000..73de524aa Binary files /dev/null and b/phoenix-1.6/navigation/priv/static/favicon.ico differ diff --git a/phoenix-1.6/navigation/priv/static/images/phoenix.png b/phoenix-1.6/navigation/priv/static/images/phoenix.png new file mode 100644 index 000000000..9c81075f6 Binary files /dev/null and b/phoenix-1.6/navigation/priv/static/images/phoenix.png differ diff --git a/phoenix-1.6/navigation/priv/static/robots.txt b/phoenix-1.6/navigation/priv/static/robots.txt new file mode 100644 index 000000000..26e06b5f1 --- /dev/null +++ b/phoenix-1.6/navigation/priv/static/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/phoenix-1.6/navigation/test/navigation_web/controllers/page_controller_test.exs b/phoenix-1.6/navigation/test/navigation_web/controllers/page_controller_test.exs new file mode 100644 index 000000000..d1888d881 --- /dev/null +++ b/phoenix-1.6/navigation/test/navigation_web/controllers/page_controller_test.exs @@ -0,0 +1,8 @@ +defmodule NavigationWeb.PageControllerTest do + use NavigationWeb.ConnCase + + test "GET /", %{conn: conn} do + conn = get(conn, "/") + assert html_response(conn, 200) =~ "Welcome to Phoenix!" + end +end diff --git a/phoenix-1.6/navigation/test/navigation_web/views/error_view_test.exs b/phoenix-1.6/navigation/test/navigation_web/views/error_view_test.exs new file mode 100644 index 000000000..948c35664 --- /dev/null +++ b/phoenix-1.6/navigation/test/navigation_web/views/error_view_test.exs @@ -0,0 +1,14 @@ +defmodule NavigationWeb.ErrorViewTest do + use NavigationWeb.ConnCase, async: true + + # Bring render/3 and render_to_string/3 for testing custom views + import Phoenix.View + + test "renders 404.html" do + assert render_to_string(NavigationWeb.ErrorView, "404.html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(NavigationWeb.ErrorView, "500.html", []) == "Internal Server Error" + end +end diff --git a/phoenix-1.6/navigation/test/navigation_web/views/layout_view_test.exs b/phoenix-1.6/navigation/test/navigation_web/views/layout_view_test.exs new file mode 100644 index 000000000..38e608b66 --- /dev/null +++ b/phoenix-1.6/navigation/test/navigation_web/views/layout_view_test.exs @@ -0,0 +1,8 @@ +defmodule NavigationWeb.LayoutViewTest do + use NavigationWeb.ConnCase, async: true + + # When testing helpers, you may want to import Phoenix.HTML and + # use functions such as safe_to_string() to convert the helper + # result into an HTML string. + # import Phoenix.HTML +end diff --git a/phoenix-1.6/navigation/test/navigation_web/views/page_view_test.exs b/phoenix-1.6/navigation/test/navigation_web/views/page_view_test.exs new file mode 100644 index 000000000..84c20f015 --- /dev/null +++ b/phoenix-1.6/navigation/test/navigation_web/views/page_view_test.exs @@ -0,0 +1,3 @@ +defmodule NavigationWeb.PageViewTest do + use NavigationWeb.ConnCase, async: true +end diff --git a/phoenix-1.6/navigation/test/support/conn_case.ex b/phoenix-1.6/navigation/test/support/conn_case.ex new file mode 100644 index 000000000..65f78fede --- /dev/null +++ b/phoenix-1.6/navigation/test/support/conn_case.ex @@ -0,0 +1,37 @@ +defmodule NavigationWeb.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use NavigationWeb.ConnCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import NavigationWeb.ConnCase + + alias NavigationWeb.Router.Helpers, as: Routes + + # The default endpoint for testing + @endpoint NavigationWeb.Endpoint + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/phoenix-1.6/navigation/test/test_helper.exs b/phoenix-1.6/navigation/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/phoenix-1.6/navigation/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/phoenix-1.6/random_number/.formatter.exs b/phoenix-1.6/random_number/.formatter.exs new file mode 100644 index 000000000..47616780b --- /dev/null +++ b/phoenix-1.6/random_number/.formatter.exs @@ -0,0 +1,4 @@ +[ + import_deps: [:phoenix], + inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/phoenix-1.6/random_number/.gitignore b/phoenix-1.6/random_number/.gitignore new file mode 100644 index 000000000..808a2cbbe --- /dev/null +++ b/phoenix-1.6/random_number/.gitignore @@ -0,0 +1,34 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +random_number-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + diff --git a/phoenix-1.6/random_number/README.md b/phoenix-1.6/random_number/README.md new file mode 100644 index 000000000..9c5c6e3f5 --- /dev/null +++ b/phoenix-1.6/random_number/README.md @@ -0,0 +1,18 @@ +# RandomNumber + +To start your Phoenix server: + + * Install dependencies with `mix deps.get` + * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + + * Official website: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/phoenix-1.6/random_number/assets/css/app.css b/phoenix-1.6/random_number/assets/css/app.css new file mode 100644 index 000000000..19c2e51ed --- /dev/null +++ b/phoenix-1.6/random_number/assets/css/app.css @@ -0,0 +1,120 @@ +/* This file is for your main application CSS */ +@import "./phoenix.css"; + +/* Alerts and form errors used by phx.new */ +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert p { + margin-bottom: 0; +} +.alert:empty { + display: none; +} +.invalid-feedback { + color: #a94442; + display: block; + margin: -1rem 0 2rem; +} + +/* LiveView specific classes for your customization */ +.phx-no-feedback.invalid-feedback, +.phx-no-feedback .invalid-feedback { + display: none; +} + +.phx-click-loading { + opacity: 0.5; + transition: opacity 1s ease-out; +} + +.phx-loading{ + cursor: wait; +} + +.phx-modal { + opacity: 1!important; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.4); +} + +.phx-modal-content { + background-color: #fefefe; + margin: 15vh auto; + padding: 20px; + border: 1px solid #888; + width: 80%; +} + +.phx-modal-close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; +} + +.phx-modal-close:hover, +.phx-modal-close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + +.fade-in-scale { + animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys; +} + +.fade-out-scale { + animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys; +} + +.fade-in { + animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys; +} +.fade-out { + animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys; +} + +@keyframes fade-in-scale-keys{ + 0% { scale: 0.95; opacity: 0; } + 100% { scale: 1.0; opacity: 1; } +} + +@keyframes fade-out-scale-keys{ + 0% { scale: 1.0; opacity: 1; } + 100% { scale: 0.95; opacity: 0; } +} + +@keyframes fade-in-keys{ + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +@keyframes fade-out-keys{ + 0% { opacity: 1; } + 100% { opacity: 0; } +} diff --git a/phoenix-1.6/random_number/assets/css/phoenix.css b/phoenix-1.6/random_number/assets/css/phoenix.css new file mode 100644 index 000000000..0d59050f8 --- /dev/null +++ b/phoenix-1.6/random_number/assets/css/phoenix.css @@ -0,0 +1,101 @@ +/* Includes some default style for the starter application. + * This can be safely deleted to start fresh. + */ + +/* Milligram v1.4.1 https://milligram.github.io + * Copyright (c) 2020 CJ Patoilo Licensed under the MIT license + */ + +*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#000000;font-family:'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#0069d9;border:0.1rem solid #0069d9;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#0069d9;border-color:#0069d9}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#0069d9}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#0069d9}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#0069d9}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#0069d9}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #0069d9;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='color'],input[type='date'],input[type='datetime'],input[type='datetime-local'],input[type='email'],input[type='month'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],input[type='week'],input:not([type]),textarea,select{-webkit-appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem .7rem;width:100%}input[type='color']:focus,input[type='date']:focus,input[type='datetime']:focus,input[type='datetime-local']:focus,input[type='email']:focus,input[type='month']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,input[type='week']:focus,input:not([type]):focus,textarea:focus,select:focus{border-color:#0069d9;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}select[multiple]{background:none;height:auto}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-40{margin-left:40%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-60{margin-left:60%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#0069d9;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;display:block;overflow-x:auto;text-align:left;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}@media (min-width: 40rem){table{display:table;overflow-x:initial}}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right} + +/* General style */ +h1{font-size: 3.6rem; line-height: 1.25} +h2{font-size: 2.8rem; line-height: 1.3} +h3{font-size: 2.2rem; letter-spacing: -.08rem; line-height: 1.35} +h4{font-size: 1.8rem; letter-spacing: -.05rem; line-height: 1.5} +h5{font-size: 1.6rem; letter-spacing: 0; line-height: 1.4} +h6{font-size: 1.4rem; letter-spacing: 0; line-height: 1.2} +pre{padding: 1em;} + +.container{ + margin: 0 auto; + max-width: 80.0rem; + padding: 0 2.0rem; + position: relative; + width: 100% +} +select { + width: auto; +} + +/* Phoenix promo and logo */ +.phx-hero { + text-align: center; + border-bottom: 1px solid #e3e3e3; + background: #eee; + border-radius: 6px; + padding: 3em 3em 1em; + margin-bottom: 3rem; + font-weight: 200; + font-size: 120%; +} +.phx-hero input { + background: #ffffff; +} +.phx-logo { + min-width: 300px; + margin: 1rem; + display: block; +} +.phx-logo img { + width: auto; + display: block; +} + +/* Headers */ +header { + width: 100%; + background: #fdfdfd; + border-bottom: 1px solid #eaeaea; + margin-bottom: 2rem; +} +header section { + align-items: center; + display: flex; + flex-direction: column; + justify-content: space-between; +} +header section :first-child { + order: 2; +} +header section :last-child { + order: 1; +} +header nav ul, +header nav li { + margin: 0; + padding: 0; + display: block; + text-align: right; + white-space: nowrap; +} +header nav ul { + margin: 1rem; + margin-top: 0; +} +header nav a { + display: block; +} + +@media (min-width: 40.0rem) { /* Small devices (landscape phones, 576px and up) */ + header section { + flex-direction: row; + } + header nav ul { + margin: 1rem; + } + .phx-logo { + flex-basis: 527px; + margin: 2rem 1rem; + } +} diff --git a/phoenix-1.6/random_number/assets/js/app.js b/phoenix-1.6/random_number/assets/js/app.js new file mode 100644 index 000000000..2ca06a566 --- /dev/null +++ b/phoenix-1.6/random_number/assets/js/app.js @@ -0,0 +1,45 @@ +// We import the CSS which is extracted to its own file by esbuild. +// Remove this line if you add a your own CSS build pipeline (e.g postcss). +import "../css/app.css" + +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import topbar from "../vendor/topbar" + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", info => topbar.show()) +window.addEventListener("phx:page-loading-stop", info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + diff --git a/phoenix-1.6/random_number/assets/vendor/topbar.js b/phoenix-1.6/random_number/assets/vendor/topbar.js new file mode 100644 index 000000000..1f6220974 --- /dev/null +++ b/phoenix-1.6/random_number/assets/vendor/topbar.js @@ -0,0 +1,157 @@ +/** + * @license MIT + * topbar 1.0.0, 2021-01-06 + * https://buunguyen.github.io/topbar + * Copyright (c) 2021 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + // https://gist.github.com/paulirish/1579671 + (function () { + var lastTime = 0; + var vendors = ["ms", "moz", "webkit", "o"]; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x] + "RequestAnimationFrame"]; + window.cancelAnimationFrame = + window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"]; + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + })(); + + var canvas, + progressTimerId, + fadeTimerId, + currentProgress, + showing, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + document.body.appendChild(canvas); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function () { + if (showing) return; + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/phoenix-1.6/random_number/config/config.exs b/phoenix-1.6/random_number/config/config.exs new file mode 100644 index 000000000..42341eaee --- /dev/null +++ b/phoenix-1.6/random_number/config/config.exs @@ -0,0 +1,49 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +# Configures the endpoint +config :random_number, RandomNumberWeb.Endpoint, + url: [host: "localhost"], + render_errors: [view: RandomNumberWeb.ErrorView, accepts: ~w(html json), layout: false], + pubsub_server: RandomNumber.PubSub, + live_view: [signing_salt: "dqVm5Fdf"] + +# Configures the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :random_number, RandomNumber.Mailer, adapter: Swoosh.Adapters.Local + +# Swoosh API client is needed for adapters other than SMTP. +config :swoosh, :api_client, false + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.14.29", + default: [ + args: + ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/phoenix-1.6/random_number/config/dev.exs b/phoenix-1.6/random_number/config/dev.exs new file mode 100644 index 000000000..b763da167 --- /dev/null +++ b/phoenix-1.6/random_number/config/dev.exs @@ -0,0 +1,65 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we use it +# with esbuild to bundle .js and .css sources. +config :random_number, RandomNumberWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "NNnBf0nJ1YQqO3xlQpVi2IHExlE2jR0pNxyEFOnKZuVF1pWpsCI4JuVYriaq/kRT", + watchers: [ + # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args) + esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Note that this task requires Erlang/OTP 20 or later. +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :random_number, RandomNumberWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/random_number_web/(live|views)/.*(ex)$", + ~r"lib/random_number_web/templates/.*(eex)$" + ] + ] + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/phoenix-1.6/random_number/config/prod.exs b/phoenix-1.6/random_number/config/prod.exs new file mode 100644 index 000000000..4930ad897 --- /dev/null +++ b/phoenix-1.6/random_number/config/prod.exs @@ -0,0 +1,49 @@ +import Config + +# For production, don't forget to configure the url host +# to something meaningful, Phoenix uses this information +# when generating URLs. +# +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix phx.digest` task, +# which you should run after static files are built and +# before starting your production server. +config :random_number, RandomNumberWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" + +# Do not print debug messages in production +config :logger, level: :info + +# ## SSL Support +# +# To get SSL working, you will need to add the `https` key +# to the previous section and set your `:url` port to 443: +# +# config :random_number, RandomNumberWeb.Endpoint, +# ..., +# url: [host: "example.com", port: 443], +# https: [ +# ..., +# port: 443, +# cipher_suite: :strong, +# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), +# certfile: System.get_env("SOME_APP_SSL_CERT_PATH") +# ] +# +# The `cipher_suite` is set to `:strong` to support only the +# latest and more secure SSL ciphers. This means old browsers +# and clients may not be supported. You can set it to +# `:compatible` for wider support. +# +# `:keyfile` and `:certfile` expect an absolute path to the key +# and cert in disk or a relative path inside priv, for example +# "priv/ssl/server.key". For all supported SSL configuration +# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 +# +# We also recommend setting `force_ssl` in your endpoint, ensuring +# no data is ever sent via http, always redirecting to https: +# +# config :random_number, RandomNumberWeb.Endpoint, +# force_ssl: [hsts: true] +# +# Check `Plug.SSL` for all available options in `force_ssl`. diff --git a/phoenix-1.6/random_number/config/runtime.exs b/phoenix-1.6/random_number/config/runtime.exs new file mode 100644 index 000000000..8035ff2cb --- /dev/null +++ b/phoenix-1.6/random_number/config/runtime.exs @@ -0,0 +1,68 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/random_number start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :random_number, RandomNumberWeb.Endpoint, server: true +end + +if config_env() == :prod do + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + port = String.to_integer(System.get_env("PORT") || "4000") + + config :random_number, RandomNumberWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base + + # ## Configuring the mailer + # + # In production you need to configure the mailer to use a different adapter. + # Also, you may need to configure the Swoosh API client of your choice if you + # are not using SMTP. Here is an example of the configuration: + # + # config :random_number, RandomNumber.Mailer, + # adapter: Swoosh.Adapters.Mailgun, + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("MAILGUN_DOMAIN") + # + # For this example you need include a HTTP client required by Swoosh API client. + # Swoosh supports Hackney and Finch out of the box: + # + # config :swoosh, :api_client, Swoosh.ApiClient.Hackney + # + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. +end diff --git a/phoenix-1.6/random_number/config/test.exs b/phoenix-1.6/random_number/config/test.exs new file mode 100644 index 000000000..a83d47823 --- /dev/null +++ b/phoenix-1.6/random_number/config/test.exs @@ -0,0 +1,18 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :random_number, RandomNumberWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "0k0qqH+QEs2L2geoma/UH7GsLfi67THVnVa1WtnVy/bmUOwAbl3e28YQjP3jQl3J", + server: false + +# In test we don't send emails. +config :random_number, RandomNumber.Mailer, + adapter: Swoosh.Adapters.Test + +# Print only warnings and errors during test +config :logger, level: :warn + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/phoenix-1.6/random_number/lib/random_number.ex b/phoenix-1.6/random_number/lib/random_number.ex new file mode 100644 index 000000000..b891e071d --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number.ex @@ -0,0 +1,14 @@ +defmodule RandomNumber do + @moduledoc """ + RandomNumber keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ + + def random_number() do + Enum.random(1..100) + end + +end diff --git a/phoenix-1.6/random_number/lib/random_number/application.ex b/phoenix-1.6/random_number/lib/random_number/application.ex new file mode 100644 index 000000000..86eb38f84 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number/application.ex @@ -0,0 +1,34 @@ +defmodule RandomNumber.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + # Start the Telemetry supervisor + RandomNumberWeb.Telemetry, + # Start the PubSub system + {Phoenix.PubSub, name: RandomNumber.PubSub}, + # Start the Endpoint (http/https) + RandomNumberWeb.Endpoint + # Start a worker by calling: RandomNumber.Worker.start_link(arg) + # {RandomNumber.Worker, arg} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: RandomNumber.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + RandomNumberWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/phoenix-1.6/random_number/lib/random_number/mailer.ex b/phoenix-1.6/random_number/lib/random_number/mailer.ex new file mode 100644 index 000000000..27ead547d --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number/mailer.ex @@ -0,0 +1,3 @@ +defmodule RandomNumber.Mailer do + use Swoosh.Mailer, otp_app: :random_number +end diff --git a/phoenix-1.6/random_number/lib/random_number_web.ex b/phoenix-1.6/random_number/lib/random_number_web.ex new file mode 100644 index 000000000..4eb06eddb --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web.ex @@ -0,0 +1,110 @@ +defmodule RandomNumberWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use RandomNumberWeb, :controller + use RandomNumberWeb, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define any helper function in modules + and import those modules here. + """ + + def controller do + quote do + use Phoenix.Controller, namespace: RandomNumberWeb + + import Plug.Conn + import RandomNumberWeb.Gettext + alias RandomNumberWeb.Router.Helpers, as: Routes + end + end + + def view do + quote do + use Phoenix.View, + root: "lib/random_number_web/templates", + namespace: RandomNumberWeb + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] + + # Include shared imports and aliases for views + unquote(view_helpers()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {RandomNumberWeb.LayoutView, "live.html"} + + unquote(view_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(view_helpers()) + end + end + + def component do + quote do + use Phoenix.Component + + unquote(view_helpers()) + end + end + + def router do + quote do + use Phoenix.Router + + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + import RandomNumberWeb.Gettext + end + end + + defp view_helpers do + quote do + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) + import Phoenix.LiveView.Helpers + + # Import basic rendering functionality (render, render_layout, etc) + import Phoenix.View + + import RandomNumberWeb.ErrorHelpers + import RandomNumberWeb.Gettext + alias RandomNumberWeb.Router.Helpers, as: Routes + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/controllers/page_controller.ex b/phoenix-1.6/random_number/lib/random_number_web/controllers/page_controller.ex new file mode 100644 index 000000000..e3c8667be --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/controllers/page_controller.ex @@ -0,0 +1,8 @@ +defmodule RandomNumberWeb.PageController do + use RandomNumberWeb, :controller + + def index(conn, _params) do + render(conn, "index.html") + end + +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/controllers/random_number_controller.ex b/phoenix-1.6/random_number/lib/random_number_web/controllers/random_number_controller.ex new file mode 100644 index 000000000..c3f1085dd --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/controllers/random_number_controller.ex @@ -0,0 +1,7 @@ +defmodule RandomNumberWeb.RandomNumberController do + use RandomNumberWeb, :controller + + def random_number(conn, _params) do + render(conn, "random_number.html") + end +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/endpoint.ex b/phoenix-1.6/random_number/lib/random_number_web/endpoint.ex new file mode 100644 index 000000000..71459ab55 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/endpoint.ex @@ -0,0 +1,49 @@ +defmodule RandomNumberWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :random_number + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_random_number_key", + signing_salt: "24TE+CBv" + ] + + socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phx.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", + from: :random_number, + gzip: false, + only: ~w(assets fonts images favicon.ico robots.txt) + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug RandomNumberWeb.Router +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/gettext.ex b/phoenix-1.6/random_number/lib/random_number_web/gettext.ex new file mode 100644 index 000000000..1b54ee342 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule RandomNumberWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import RandomNumberWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :random_number +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/router.ex b/phoenix-1.6/random_number/lib/random_number_web/router.ex new file mode 100644 index 000000000..29fac6447 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/router.ex @@ -0,0 +1,58 @@ +defmodule RandomNumberWeb.Router do + use RandomNumberWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, {RandomNumberWeb.LayoutView, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", RandomNumberWeb do + pipe_through :browser + + get "/", PageController, :index + + get "/random_number", RandomNumberController, :random_number + end + + # Other scopes may use custom stacks. + # scope "/api", RandomNumberWeb do + # pipe_through :api + # end + + # Enables LiveDashboard only for development + # + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + if Mix.env() in [:dev, :test] do + import Phoenix.LiveDashboard.Router + + scope "/" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: RandomNumberWeb.Telemetry + end + end + + # Enables the Swoosh mailbox preview in development. + # + # Note that preview only shows emails that were sent by the same + # node running the Phoenix server. + if Mix.env() == :dev do + scope "/dev" do + pipe_through :browser + + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/telemetry.ex b/phoenix-1.6/random_number/lib/random_number_web/telemetry.ex new file mode 100644 index 000000000..9bf9de878 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/telemetry.ex @@ -0,0 +1,48 @@ +defmodule RandomNumberWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {RandomNumberWeb, :count_users, []} + ] + end +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/templates/layout/app.html.heex b/phoenix-1.6/random_number/lib/random_number_web/templates/layout/app.html.heex new file mode 100644 index 000000000..169aed956 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/templates/layout/app.html.heex @@ -0,0 +1,5 @@ +
+ + + <%= @inner_content %> +
diff --git a/phoenix-1.6/random_number/lib/random_number_web/templates/layout/live.html.heex b/phoenix-1.6/random_number/lib/random_number_web/templates/layout/live.html.heex new file mode 100644 index 000000000..a29d60448 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/templates/layout/live.html.heex @@ -0,0 +1,11 @@ +
+ + + + + <%= @inner_content %> +
diff --git a/phoenix-1.6/random_number/lib/random_number_web/templates/layout/root.html.heex b/phoenix-1.6/random_number/lib/random_number_web/templates/layout/root.html.heex new file mode 100644 index 000000000..4c84ba771 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/templates/layout/root.html.heex @@ -0,0 +1,30 @@ + + + + + + + + <%= live_title_tag assigns[:page_title] || "RandomNumber", suffix: " Ā· Phoenix Framework" %> + + + + +
+
+ + +
+
+ <%= @inner_content %> + + diff --git a/phoenix-1.6/random_number/lib/random_number_web/templates/page/index.html.heex b/phoenix-1.6/random_number/lib/random_number_web/templates/page/index.html.heex new file mode 100644 index 000000000..f844bd8d7 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/templates/page/index.html.heex @@ -0,0 +1,41 @@ +
+

<%= gettext "Welcome to %{name}!", name: "Phoenix" %>

+

Peace of mind from prototype to production

+
+ +
+
+

Resources

+ +
+
+

Help

+ +
+
diff --git a/phoenix-1.6/random_number/lib/random_number_web/templates/random_number/random_number.html.heex b/phoenix-1.6/random_number/lib/random_number_web/templates/random_number/random_number.html.heex new file mode 100644 index 000000000..193b32d8e --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/templates/random_number/random_number.html.heex @@ -0,0 +1 @@ +

<%= RandomNumber.random_number() %>

diff --git a/phoenix-1.6/random_number/lib/random_number_web/views/error_helpers.ex b/phoenix-1.6/random_number/lib/random_number_web/views/error_helpers.ex new file mode 100644 index 000000000..088a3e2e3 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/views/error_helpers.ex @@ -0,0 +1,47 @@ +defmodule RandomNumberWeb.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + use Phoenix.HTML + + @doc """ + Generates tag for inlined form input errors. + """ + def error_tag(form, field) do + Enum.map(Keyword.get_values(form.errors, field), fn error -> + content_tag(:span, translate_error(error), + class: "invalid-feedback", + phx_feedback_for: input_name(form, field) + ) + end) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate "is invalid" in the "errors" domain + # dgettext("errors", "is invalid") + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + # This requires us to call the Gettext module passing our gettext + # backend as first argument. + # + # Note we use the "errors" domain, which means translations + # should be written to the errors.po file. The :count option is + # set by Ecto and indicates we should also apply plural rules. + if count = opts[:count] do + Gettext.dngettext(RandomNumberWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(RandomNumberWeb.Gettext, "errors", msg, opts) + end + end +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/views/error_view.ex b/phoenix-1.6/random_number/lib/random_number_web/views/error_view.ex new file mode 100644 index 000000000..45a647322 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/views/error_view.ex @@ -0,0 +1,16 @@ +defmodule RandomNumberWeb.ErrorView do + use RandomNumberWeb, :view + + # If you want to customize a particular status code + # for a certain format, you may uncomment below. + # def render("500.html", _assigns) do + # "Internal Server Error" + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.html" becomes + # "Not Found". + def template_not_found(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/views/layout_view.ex b/phoenix-1.6/random_number/lib/random_number_web/views/layout_view.ex new file mode 100644 index 000000000..7d877f817 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/views/layout_view.ex @@ -0,0 +1,7 @@ +defmodule RandomNumberWeb.LayoutView do + use RandomNumberWeb, :view + + # Phoenix LiveDashboard is available only in development by default, + # so we instruct Elixir to not warn if the dashboard route is missing. + @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}} +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/views/page_view.ex b/phoenix-1.6/random_number/lib/random_number_web/views/page_view.ex new file mode 100644 index 000000000..7b9f50750 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/views/page_view.ex @@ -0,0 +1,3 @@ +defmodule RandomNumberWeb.PageView do + use RandomNumberWeb, :view +end diff --git a/phoenix-1.6/random_number/lib/random_number_web/views/random_number_view.ex b/phoenix-1.6/random_number/lib/random_number_web/views/random_number_view.ex new file mode 100644 index 000000000..39f447933 --- /dev/null +++ b/phoenix-1.6/random_number/lib/random_number_web/views/random_number_view.ex @@ -0,0 +1,3 @@ +defmodule RandomNumberWeb.RandomNumberView do + use RandomNumberWeb, :view +end diff --git a/phoenix-1.6/random_number/mix.exs b/phoenix-1.6/random_number/mix.exs new file mode 100644 index 000000000..df66f3a70 --- /dev/null +++ b/phoenix-1.6/random_number/mix.exs @@ -0,0 +1,64 @@ +defmodule RandomNumber.MixProject do + use Mix.Project + + def project do + [ + app: :random_number, + version: "0.1.0", + elixir: "~> 1.12", + elixirc_paths: elixirc_paths(Mix.env()), + compilers: [:gettext] ++ Mix.compilers(), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {RandomNumber.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.6.15"}, + {:phoenix_html, "~> 3.0"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 0.17.5"}, + {:floki, ">= 0.30.0", only: :test}, + {:phoenix_live_dashboard, "~> 0.6"}, + {:esbuild, "~> 0.4", runtime: Mix.env() == :dev}, + {:swoosh, "~> 1.3"}, + {:telemetry_metrics, "~> 0.6"}, + {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 0.18"}, + {:jason, "~> 1.2"}, + {:plug_cowboy, "~> 2.5"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to install project dependencies and perform other setup tasks, run: + # + # $ mix setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get"], + "assets.deploy": ["esbuild default --minify", "phx.digest"] + ] + end +end diff --git a/phoenix-1.6/random_number/mix.lock b/phoenix-1.6/random_number/mix.lock new file mode 100644 index 000000000..47b126398 --- /dev/null +++ b/phoenix-1.6/random_number/mix.lock @@ -0,0 +1,29 @@ +%{ + "castore": {:hex, :castore, "1.0.1", "240b9edb4e9e94f8f56ab39d8d2d0a57f49e46c56aced8f873892df8ff64ff5a", [:mix], [], "hexpm", "b4951de93c224d44fac71614beabd88b71932d0b1dea80d2f80fb9044e01bbb3"}, + "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, + "esbuild": {:hex, :esbuild, "0.6.1", "a774bfa7b4512a1211bf15880b462be12a4c48ed753a170c68c63b2c95888150", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "569f7409fb5a932211573fc20e2a930a0d5cf3377c5b4f6506c651b1783a1678"}, + "expo": {:hex, :expo, "0.4.0", "bbe4bf455e2eb2ebd2f1e7d83530ce50fb9990eb88fc47855c515bfdf1c6626f", [:mix], [], "hexpm", "a8ed1683ec8b7c7fa53fd7a41b2c6935f539168a6bb0616d7fd6b58a36f3abf2"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"}, + "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, + "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.14", "5ec615d4d61bf9d4755f158bd6c80372b715533fe6d6219e12d74fb5eedbeac1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "afeb6ba43ce329a6f7fc1c9acdfc6d3039995345f025febb7f409a92f6faebd3"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, + "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, +} diff --git a/phoenix-1.6/random_number/priv/gettext/en/LC_MESSAGES/errors.po b/phoenix-1.6/random_number/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 000000000..cdec3a113 --- /dev/null +++ b/phoenix-1.6/random_number/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,11 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" diff --git a/phoenix-1.6/random_number/priv/gettext/errors.pot b/phoenix-1.6/random_number/priv/gettext/errors.pot new file mode 100644 index 000000000..d6f47fa87 --- /dev/null +++ b/phoenix-1.6/random_number/priv/gettext/errors.pot @@ -0,0 +1,10 @@ +## This is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here has no +## effect: edit them in PO (`.po`) files instead. + diff --git a/phoenix-1.6/random_number/priv/static/favicon.ico b/phoenix-1.6/random_number/priv/static/favicon.ico new file mode 100644 index 000000000..73de524aa Binary files /dev/null and b/phoenix-1.6/random_number/priv/static/favicon.ico differ diff --git a/phoenix-1.6/random_number/priv/static/images/phoenix.png b/phoenix-1.6/random_number/priv/static/images/phoenix.png new file mode 100644 index 000000000..9c81075f6 Binary files /dev/null and b/phoenix-1.6/random_number/priv/static/images/phoenix.png differ diff --git a/phoenix-1.6/random_number/priv/static/robots.txt b/phoenix-1.6/random_number/priv/static/robots.txt new file mode 100644 index 000000000..26e06b5f1 --- /dev/null +++ b/phoenix-1.6/random_number/priv/static/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/phoenix-1.6/random_number/test/random_number_web/controllers/page_controller_test.exs b/phoenix-1.6/random_number/test/random_number_web/controllers/page_controller_test.exs new file mode 100644 index 000000000..ba3133ec3 --- /dev/null +++ b/phoenix-1.6/random_number/test/random_number_web/controllers/page_controller_test.exs @@ -0,0 +1,8 @@ +defmodule RandomNumberWeb.PageControllerTest do + use RandomNumberWeb.ConnCase + + test "GET /", %{conn: conn} do + conn = get(conn, "/") + assert html_response(conn, 200) =~ "Welcome to Phoenix!" + end +end diff --git a/phoenix-1.6/random_number/test/random_number_web/views/error_view_test.exs b/phoenix-1.6/random_number/test/random_number_web/views/error_view_test.exs new file mode 100644 index 000000000..0c3c7c4c8 --- /dev/null +++ b/phoenix-1.6/random_number/test/random_number_web/views/error_view_test.exs @@ -0,0 +1,14 @@ +defmodule RandomNumberWeb.ErrorViewTest do + use RandomNumberWeb.ConnCase, async: true + + # Bring render/3 and render_to_string/3 for testing custom views + import Phoenix.View + + test "renders 404.html" do + assert render_to_string(RandomNumberWeb.ErrorView, "404.html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(RandomNumberWeb.ErrorView, "500.html", []) == "Internal Server Error" + end +end diff --git a/phoenix-1.6/random_number/test/random_number_web/views/layout_view_test.exs b/phoenix-1.6/random_number/test/random_number_web/views/layout_view_test.exs new file mode 100644 index 000000000..7a032de40 --- /dev/null +++ b/phoenix-1.6/random_number/test/random_number_web/views/layout_view_test.exs @@ -0,0 +1,8 @@ +defmodule RandomNumberWeb.LayoutViewTest do + use RandomNumberWeb.ConnCase, async: true + + # When testing helpers, you may want to import Phoenix.HTML and + # use functions such as safe_to_string() to convert the helper + # result into an HTML string. + # import Phoenix.HTML +end diff --git a/phoenix-1.6/random_number/test/random_number_web/views/page_view_test.exs b/phoenix-1.6/random_number/test/random_number_web/views/page_view_test.exs new file mode 100644 index 000000000..26ed22954 --- /dev/null +++ b/phoenix-1.6/random_number/test/random_number_web/views/page_view_test.exs @@ -0,0 +1,3 @@ +defmodule RandomNumberWeb.PageViewTest do + use RandomNumberWeb.ConnCase, async: true +end diff --git a/phoenix-1.6/random_number/test/support/conn_case.ex b/phoenix-1.6/random_number/test/support/conn_case.ex new file mode 100644 index 000000000..c0b572fc2 --- /dev/null +++ b/phoenix-1.6/random_number/test/support/conn_case.ex @@ -0,0 +1,37 @@ +defmodule RandomNumberWeb.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use RandomNumberWeb.ConnCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import RandomNumberWeb.ConnCase + + alias RandomNumberWeb.Router.Helpers, as: Routes + + # The default endpoint for testing + @endpoint RandomNumberWeb.Endpoint + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/phoenix-1.6/random_number/test/test_helper.exs b/phoenix-1.6/random_number/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/phoenix-1.6/random_number/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/phoenix-1.7/counter/.formatter.exs b/phoenix-1.7/counter/.formatter.exs new file mode 100644 index 000000000..e945e12b9 --- /dev/null +++ b/phoenix-1.7/counter/.formatter.exs @@ -0,0 +1,5 @@ +[ + import_deps: [:phoenix], + plugins: [Phoenix.LiveView.HTMLFormatter], + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] +] diff --git a/phoenix-1.7/counter/.gitignore b/phoenix-1.7/counter/.gitignore new file mode 100644 index 000000000..bd521da14 --- /dev/null +++ b/phoenix-1.7/counter/.gitignore @@ -0,0 +1,37 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Temporary files, for example, from tests. +/tmp/ + +# Ignore package tarball (built via "mix hex.build"). +counter-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + diff --git a/phoenix-1.7/counter/README.md b/phoenix-1.7/counter/README.md new file mode 100644 index 000000000..b8a1d7779 --- /dev/null +++ b/phoenix-1.7/counter/README.md @@ -0,0 +1,18 @@ +# Counter + +To start your Phoenix server: + + * Run `mix setup` to install and setup dependencies + * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + + * Official website: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/phoenix-1.7/counter/assets/css/app.css b/phoenix-1.7/counter/assets/css/app.css new file mode 100644 index 000000000..378c8f905 --- /dev/null +++ b/phoenix-1.7/counter/assets/css/app.css @@ -0,0 +1,5 @@ +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + +/* This file is for your main application CSS */ diff --git a/phoenix-1.7/counter/assets/js/app.js b/phoenix-1.7/counter/assets/js/app.js new file mode 100644 index 000000000..df0cdd9f6 --- /dev/null +++ b/phoenix-1.7/counter/assets/js/app.js @@ -0,0 +1,41 @@ +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import topbar from "../vendor/topbar" + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) +window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + diff --git a/phoenix-1.7/counter/assets/tailwind.config.js b/phoenix-1.7/counter/assets/tailwind.config.js new file mode 100644 index 000000000..cc9e6a276 --- /dev/null +++ b/phoenix-1.7/counter/assets/tailwind.config.js @@ -0,0 +1,67 @@ +// See the Tailwind configuration guide for advanced usage +// https://tailwindcss.com/docs/configuration + +const plugin = require("tailwindcss/plugin") +const fs = require("fs") +const path = require("path") + +module.exports = { + content: [ + "./js/**/*.js", + "../lib/*_web.ex", + "../lib/*_web/**/*.*ex" + ], + theme: { + extend: { + colors: { + brand: "#FD4F00", + } + }, + }, + plugins: [ + require("@tailwindcss/forms"), + // Allows prefixing tailwind classes with LiveView classes to add rules + // only when LiveView classes are applied, for example: + // + //
+ // + plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])), + plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])), + plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])), + plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])), + + // Embeds Hero Icons (https://heroicons.com) into your app.css bundle + // See your `CoreComponents.icon/1` for more information. + // + plugin(function({matchComponents, theme}) { + let iconsDir = path.join(__dirname, "../priv/hero_icons/optimized") + let values = {} + let icons = [ + ["", "/24/outline"], + ["-solid", "/24/solid"], + ["-mini", "/20/solid"] + ] + icons.forEach(([suffix, dir]) => { + fs.readdirSync(path.join(iconsDir, dir)).map(file => { + let name = path.basename(file, ".svg") + suffix + values[name] = {name, fullPath: path.join(iconsDir, dir, file)} + }) + }) + matchComponents({ + "hero": ({name, fullPath}) => { + let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") + return { + [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, + "-webkit-mask": `var(--hero-${name})`, + "mask": `var(--hero-${name})`, + "background-color": "currentColor", + "vertical-align": "middle", + "display": "inline-block", + "width": theme("spacing.5"), + "height": theme("spacing.5") + } + } + }, {values}) + }) + ] +} diff --git a/phoenix-1.7/counter/assets/vendor/topbar.js b/phoenix-1.7/counter/assets/vendor/topbar.js new file mode 100644 index 000000000..41957274d --- /dev/null +++ b/phoenix-1.7/counter/assets/vendor/topbar.js @@ -0,0 +1,165 @@ +/** + * @license MIT + * topbar 2.0.0, 2023-02-04 + * https://buunguyen.github.io/topbar + * Copyright (c) 2021 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + // https://gist.github.com/paulirish/1579671 + (function () { + var lastTime = 0; + var vendors = ["ms", "moz", "webkit", "o"]; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x] + "RequestAnimationFrame"]; + window.cancelAnimationFrame = + window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"]; + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + })(); + + var canvas, + currentProgress, + showing, + progressTimerId = null, + fadeTimerId = null, + delayTimerId = null, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + document.body.appendChild(canvas); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function (delay) { + if (showing) return; + if (delay) { + if (delayTimerId) return; + delayTimerId = setTimeout(() => topbar.show(), delay); + } else { + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + clearTimeout(delayTimerId); + delayTimerId = null; + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/phoenix-1.7/counter/config/config.exs b/phoenix-1.7/counter/config/config.exs new file mode 100644 index 000000000..2708337d6 --- /dev/null +++ b/phoenix-1.7/counter/config/config.exs @@ -0,0 +1,61 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +# Configures the endpoint +config :counter, CounterWeb.Endpoint, + url: [host: "localhost"], + render_errors: [ + formats: [html: CounterWeb.ErrorHTML, json: CounterWeb.ErrorJSON], + layout: false + ], + pubsub_server: Counter.PubSub, + live_view: [signing_salt: "DB1nH+Sn"] + +# Configures the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :counter, Counter.Mailer, adapter: Swoosh.Adapters.Local + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.14.41", + default: [ + args: + ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] + +# Configure tailwind (the version is required) +config :tailwind, + version: "3.2.4", + default: [ + args: ~w( + --config=tailwind.config.js + --input=css/app.css + --output=../priv/static/assets/app.css + ), + cd: Path.expand("../assets", __DIR__) + ] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/phoenix-1.7/counter/config/dev.exs b/phoenix-1.7/counter/config/dev.exs new file mode 100644 index 000000000..3768bba41 --- /dev/null +++ b/phoenix-1.7/counter/config/dev.exs @@ -0,0 +1,69 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we use it +# with esbuild to bundle .js and .css sources. +config :counter, CounterWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "KmPkbfiVqpsyJHpm+RlNHgX85D1Fhj5vXUpdASRh2JBWNcQDWa7h8Fb6ft59rCW8", + watchers: [ + esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}, + tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :counter, CounterWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/counter_web/(controllers|live|components)/.*(ex|heex)$" + ] + ] + +# Enable dev routes for dashboard and mailbox +config :counter, dev_routes: true + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime + +# Disable swoosh api client as it is only required for production adapters. +config :swoosh, :api_client, false diff --git a/phoenix-1.7/counter/config/prod.exs b/phoenix-1.7/counter/config/prod.exs new file mode 100644 index 000000000..a713ea1be --- /dev/null +++ b/phoenix-1.7/counter/config/prod.exs @@ -0,0 +1,21 @@ +import Config + +# For production, don't forget to configure the url host +# to something meaningful, Phoenix uses this information +# when generating URLs. + +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix phx.digest` task, +# which you should run after static files are built and +# before starting your production server. +config :counter, CounterWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" + +# Configures Swoosh API Client +config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Counter.Finch + +# Do not print debug messages in production +config :logger, level: :info + +# Runtime production configuration, including reading +# of environment variables, is done on config/runtime.exs. diff --git a/phoenix-1.7/counter/config/runtime.exs b/phoenix-1.7/counter/config/runtime.exs new file mode 100644 index 000000000..dcd5a8f93 --- /dev/null +++ b/phoenix-1.7/counter/config/runtime.exs @@ -0,0 +1,100 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/counter start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :counter, CounterWeb.Endpoint, server: true +end + +if config_env() == :prod do + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + port = String.to_integer(System.get_env("PORT") || "4000") + + config :counter, CounterWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base + + # ## SSL Support + # + # To get SSL working, you will need to add the `https` key + # to your endpoint configuration: + # + # config :counter, CounterWeb.Endpoint, + # https: [ + # ..., + # port: 443, + # cipher_suite: :strong, + # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), + # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") + # ] + # + # The `cipher_suite` is set to `:strong` to support only the + # latest and more secure SSL ciphers. This means old browsers + # and clients may not be supported. You can set it to + # `:compatible` for wider support. + # + # `:keyfile` and `:certfile` expect an absolute path to the key + # and cert in disk or a relative path inside priv, for example + # "priv/ssl/server.key". For all supported SSL configuration + # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 + # + # We also recommend setting `force_ssl` in your endpoint, ensuring + # no data is ever sent via http, always redirecting to https: + # + # config :counter, CounterWeb.Endpoint, + # force_ssl: [hsts: true] + # + # Check `Plug.SSL` for all available options in `force_ssl`. + + # ## Configuring the mailer + # + # In production you need to configure the mailer to use a different adapter. + # Also, you may need to configure the Swoosh API client of your choice if you + # are not using SMTP. Here is an example of the configuration: + # + # config :counter, Counter.Mailer, + # adapter: Swoosh.Adapters.Mailgun, + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("MAILGUN_DOMAIN") + # + # For this example you need include a HTTP client required by Swoosh API client. + # Swoosh supports Hackney and Finch out of the box: + # + # config :swoosh, :api_client, Swoosh.ApiClient.Hackney + # + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. +end diff --git a/phoenix-1.7/counter/config/test.exs b/phoenix-1.7/counter/config/test.exs new file mode 100644 index 000000000..412f6fd6e --- /dev/null +++ b/phoenix-1.7/counter/config/test.exs @@ -0,0 +1,21 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :counter, CounterWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "C0EAAkvHuDfPxDguT8F8Ck8IQLBBSuOhu1hyJ1KPxapR+obidRpl5fxMqJFWusvA", + server: false + +# In test we don't send emails. +config :counter, Counter.Mailer, + adapter: Swoosh.Adapters.Test + +# Disable swoosh api client as it is only required for production adapters. +config :swoosh, :api_client, false + +# Print only warnings and errors during test +config :logger, level: :warning + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime diff --git a/phoenix-1.7/counter/lib/counter.ex b/phoenix-1.7/counter/lib/counter.ex new file mode 100644 index 000000000..bf4261567 --- /dev/null +++ b/phoenix-1.7/counter/lib/counter.ex @@ -0,0 +1,9 @@ +defmodule Counter do + @moduledoc """ + Counter keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/phoenix-1.7/counter/lib/counter/application.ex b/phoenix-1.7/counter/lib/counter/application.ex new file mode 100644 index 000000000..7e6b37691 --- /dev/null +++ b/phoenix-1.7/counter/lib/counter/application.ex @@ -0,0 +1,36 @@ +defmodule Counter.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + # Start the Telemetry supervisor + CounterWeb.Telemetry, + # Start the PubSub system + {Phoenix.PubSub, name: Counter.PubSub}, + # Start Finch + {Finch, name: Counter.Finch}, + # Start the Endpoint (http/https) + CounterWeb.Endpoint + # Start a worker by calling: Counter.Worker.start_link(arg) + # {Counter.Worker, arg} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Counter.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + CounterWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/phoenix-1.7/counter/lib/counter/mailer.ex b/phoenix-1.7/counter/lib/counter/mailer.ex new file mode 100644 index 000000000..fa6f9cda4 --- /dev/null +++ b/phoenix-1.7/counter/lib/counter/mailer.ex @@ -0,0 +1,3 @@ +defmodule Counter.Mailer do + use Swoosh.Mailer, otp_app: :counter +end diff --git a/phoenix-1.7/counter/lib/counter_web.ex b/phoenix-1.7/counter/lib/counter_web.ex new file mode 100644 index 000000000..2f231711e --- /dev/null +++ b/phoenix-1.7/counter/lib/counter_web.ex @@ -0,0 +1,113 @@ +defmodule CounterWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, components, channels, and so on. + + This can be used in your application as: + + use CounterWeb, :controller + use CounterWeb, :html + + The definitions below will be executed for every controller, + component, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define additional modules and import + those modules here. + """ + + def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + + def router do + quote do + use Phoenix.Router, helpers: false + + # Import common connection and controller functions to use in pipelines + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + end + end + + def controller do + quote do + use Phoenix.Controller, + formats: [:html, :json], + layouts: [html: CounterWeb.Layouts] + + import Plug.Conn + import CounterWeb.Gettext + + unquote(verified_routes()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {CounterWeb.Layouts, :app} + + unquote(html_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(html_helpers()) + end + end + + def html do + quote do + use Phoenix.Component + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_csrf_token: 0, view_module: 1, view_template: 1] + + # Include general helpers for rendering HTML + unquote(html_helpers()) + end + end + + defp html_helpers do + quote do + # HTML escaping functionality + import Phoenix.HTML + # Core UI components and translation + import CounterWeb.CoreComponents + import CounterWeb.Gettext + + # Shortcut for generating JS commands + alias Phoenix.LiveView.JS + + # Routes generation with the ~p sigil + unquote(verified_routes()) + end + end + + def verified_routes do + quote do + use Phoenix.VerifiedRoutes, + endpoint: CounterWeb.Endpoint, + router: CounterWeb.Router, + statics: CounterWeb.static_paths() + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/phoenix-1.7/counter/lib/counter_web/components/core_components.ex b/phoenix-1.7/counter/lib/counter_web/components/core_components.ex new file mode 100644 index 000000000..1d9ef1466 --- /dev/null +++ b/phoenix-1.7/counter/lib/counter_web/components/core_components.ex @@ -0,0 +1,689 @@ +defmodule CounterWeb.CoreComponents do + @moduledoc """ + Provides core UI components. + + The components in this module use Tailwind CSS, a utility-first CSS framework. + See the [Tailwind CSS documentation](https://tailwindcss.com) to learn how to + customize the generated components in this module. + + Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. + """ + use Phoenix.Component + + alias Phoenix.LiveView.JS + import CounterWeb.Gettext + + @doc """ + Renders a modal. + + ## Examples + + <.modal id="confirm-modal"> + Are you sure? + <:confirm>OK + <:cancel>Cancel + + + JS commands may be passed to the `:on_cancel` and `on_confirm` attributes + for the caller to react to each button press, for example: + + <.modal id="confirm" on_confirm={JS.push("delete")} on_cancel={JS.navigate(~p"/posts")}> + Are you sure you? + <:confirm>OK + <:cancel>Cancel + + """ + attr :id, :string, required: true + attr :show, :boolean, default: false + attr :on_cancel, JS, default: %JS{} + attr :on_confirm, JS, default: %JS{} + + slot :inner_block, required: true + slot :title + slot :subtitle + slot :confirm + slot :cancel + + def modal(assigns) do + ~H""" +