Git Product home page Git Product logo

todo_trek's Introduction

TodoTrek

A trello-like todo board which shows off different dynamic form strategies with Phoenix LiveView.

Dynamic Forms

The logged-in home page is the main todo dashboard. It contains sortable Lists, which are stream-based and can be re-ordered. Within each list, Todos can be manged and re-ordered. Each todo is implemented as an individual form.

Dynamic Nested Forms

The new List and edit List pages show examples of traditional nested forms with a dynamic inputs_for for List notifications. The notification entries can be prepended, appended, re-ordered, and deleted using regular checkboxes and Ecto's new sort_param and drop_param options to cast_assoc and cast_embed.

To start your Phoenix server:

  • Run mix setup to install and setup dependencies, and seed data
  • Start Phoenix endpoint with mix phx.server or inside IEx with iex -S mix phx.server
  • The default user is [email protected] with password password as the password.
  • Initial dummy data is seeded for the default user

Now you can visit localhost:4000 from your browser.

Ready to run in production? Please check our deployment guides.

Learn more

todo_trek's People

Contributors

chrismccord avatar jeregrine avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

todo_trek's Issues

Demo video looks great but code does fail with a js error after login

Just wanted to check this out during my lunch break. I run into the following js error after login:

dom_patch.js:103 Uncaught TypeError: deleteIds.forEach is not a function
    at dom_patch.js:103:19
    at Set.forEach (<anonymous>)
    at dom_patch.js:101:20
    at LiveSocket.time (live_socket.js:253:59)
    at DOMPatch.perform (dom_patch.js:100:16)
    at View.performPatch (view.js:405:11)
    at View.applyJoinPatch (view.js:331:10)
    at View.onJoinComplete (view.js:308:14)
    at view.js:272:14
    at View.applyDiff (view.js:241:5)

I updated all dependencies just to see if it changes anything. It does not. Sadly I do not have the time right now to investigate this any further. Just wanted to let you @chrismccord know right away, given the related blog post.

Bug when scrolling backwards and then forward in infinite scroll

Hi, Chris!

Thank you for releasing todo_trek; it's a really cool application, and is an excellent learning tool for all of us in the LiveView community.

I encountered a bug when scrolling backwards and then forwards with the infinite scroll in my own application, and was able to reproduce it in todo_trek (I'm essentially re-implementing infinite scroll just like it's done in todo_trek in my own app - thank you for the awesome example). I've created a fork of todo_trek whose only difference is it prints out the ActivityLog.Entry.id at the start of each log entry to highlight the problem and so the problem can be seen more easily. To reproduce the bug:

  1. Git clone my fork of todo_trek

  2. Create some activity log entries, either programmatically or via some activity in the app; I see the problem with 150 entries in my local app, but I think you'll probably see the issue with anything more than 80+ entries

  3. Scroll backwards to the beginning of time

  4. Now scroll back upwards. Note how the activity IDs go from 57, 58, 59, 60, and then jump to IDs 2, 3, 4, 5 ...

Here's a little screen capture movie which shows the issue in action (at second 35 of the 40 second video).

I'm not sure if the problem is in todo_trek, or in the underlying LiveView code, but my thought is you would probably quickly know what the issue is.

Thank you very much for your help with this,
Greg

Error when logging in with another user

When I signup another user I get this error:

key :activity_logs not found in: %{__changed__: MapSet.new([:lists]), __configured__: %{}, __ref__: 1, 
lists: %Phoenix.LiveView.LiveStream{name: :lists, dom_id: #Function<3.87984938/1 in 
Phoenix.LiveView.LiveStream.new/4>, ref: "0", inserts: [], deletes: [], reset?: false}}

KeyError at GET /

Exception:

** (KeyError) key :activity_logs not found in: %{__changed__: MapSet.new([:lists]), __configured__: %{}, __ref__: 1, lists: %Phoenix.LiveView.LiveStream{name: :lists, dom_id: #Function&lt;3.87984938/1 in Phoenix.LiveView.LiveStream.new/4&gt;, ref: "0", inserts: [], deletes: [], reset?: false}}
    (todo_trek 0.1.0) lib/todo_trek_web/live/home_live.ex:51: anonymous fn/2 in TodoTrekWeb.HomeLive.render/1
    (phoenix_live_view 0.19.0) lib/phoenix_live_view/diff.ex:385: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.0) lib/phoenix_live_view/diff.ex:537: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.19.0) lib/phoenix_live_view/diff.ex:383: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.0) lib/phoenix_live_view/diff.ex:136: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.19.0) lib/phoenix_live_view/static.ex:252: Phoenix.LiveView.Static.to_rendered_content_tag/4
    (phoenix_live_view 0.19.0) lib/phoenix_live_view/static.ex:135: Phoenix.LiveView.Static.render/3
    (phoenix_live_view 0.19.0) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
    (phoenix 1.7.2) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
    (todo_trek 0.1.0) lib/todo_trek_web/endpoint.ex:1: TodoTrekWeb.Endpoint.plug_builder_call/2
    (todo_trek 0.1.0) deps/plug/lib/plug/debugger.ex:136: TodoTrekWeb.Endpoint."call (overridable 3)"/2
    (todo_trek 0.1.0) lib/todo_trek_web/endpoint.ex:1: TodoTrekWeb.Endpoint.call/2
    (phoenix 1.7.2) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
    (plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
    (cowboy 2.9.0) /Users/joep/Repositories/cursussen/todo_trek/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
    (cowboy 2.9.0) /Users/joep/Repositories/cursussen/todo_trek/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
    (cowboy 2.9.0) /Users/joep/Repositories/cursussen/todo_trek/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
    (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

Code:

lib/todo_trek_web/live/home_live.ex

46                 list={list}
47               /&gt;
48             &lt;/div&gt;
49           &lt;/div&gt;
50         &lt;/div&gt;
51&gt;        &lt;Timeline.activity_logs stream={@streams.activity_logs} page={@page} end_of_timeline?={@end_of_timeline?}/&gt;
52       &lt;/div&gt;
53       &lt;.modal
54         :if={@live_action in [:new_list, :edit_list]}
55         id="list-modal"
56         show

lib/phoenix_live_view/diff.ex

380            changed?
381          ) do
382       {_counter, diff, children, pending, components, template} =
383         traverse_dynamic(
384           socket,
385&gt;          invoke_dynamic(rendered, false),
386           %{},
387           pending,
388           components,
389           template,
390           changed?

lib/phoenix_live_view/diff.ex

532       Enum.reduce(dynamic, {0, %{}, children, pending, components, template}, fn
533         entry, {counter, diff, children, pending, components, template} -&gt;
534           child = Map.get(children, counter)
535   
536           {serialized, child_fingerprint, pending, components, template} =
537&gt;            traverse(socket, entry, child, pending, components, template, changed?)
538   
539           # If serialized is nil, it means no changes.
540           # If it is an empty map, then it means it is a rendered struct
541           # that did not change, so we don't have to emit it either.
542           diff =

lib/enum.ex

No code available.

lib/phoenix_live_view/diff.ex

378            components,
379            template,
380            changed?
381          ) do
382       {_counter, diff, children, pending, components, template} =
383&gt;        traverse_dynamic(
384           socket,
385           invoke_dynamic(rendered, false),
386           %{},
387           pending,
388           components,

lib/phoenix_live_view/diff.ex

131       render(%{socket | fingerprints: new_fingerprints()}, rendered, new_components(uuids))
132     end
133   
134     def render(%{fingerprints: prints} = socket, %Rendered{} = rendered, components) do
135       {diff, prints, pending, components, nil} =
136&gt;        traverse(socket, rendered, prints, %{}, components, nil, true)
137   
138       # cid_to_component is used by maybe_reuse_static and it must be a copy before changes.
139       # However, given traverse does not change cid_to_component, we can read it now.
140       {cid_to_component, _, _} = components
141   

lib/phoenix_live_view/static.ex

247       Phoenix.HTML.Tag.content_tag(tag, "", attrs)
248     end
249   
250     defp to_rendered_content_tag(socket, tag, view, attrs) do
251       rendered = Utils.to_rendered(socket, view)
252&gt;      {_, diff, _} = Diff.render(socket, rendered, Diff.new_components())
253       Phoenix.HTML.Tag.content_tag(tag, {:safe, Diff.to_iodata(diff)}, attrs)
254     end
255   
256     defp load_live!(view_or_component, kind) do
257       case view_or_component.__live__() do

lib/phoenix_live_view/static.ex

130             {:data, data_attrs}
131             | extended_attrs
132           ]
133   
134           try do
135&gt;            {:ok, to_rendered_content_tag(socket, tag, view, attrs), socket.assigns}
136           catch
137             :throw, {:phoenix, :child_redirect, redirected, flash} -&gt;
138               {:stop, Utils.replace_flash(%{socket | redirected: redirected}, flash)}
139           end
140   

lib/phoenix_live_view/controller.ex

34           end
35         end
36   
37     """
38     def live_render(%Plug.Conn{} = conn, view, opts \\ []) do
39&gt;      case LiveView.Static.render(conn, view, opts) do
40         {:ok, content, socket_assigns} -&gt;
41           conn
42           |&gt; Phoenix.Controller.put_view(LiveView.Static)
43           |&gt; Phoenix.Controller.render(
44             "template.html",

lib/phoenix/router.ex

425           :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
426           halted_conn
427   
428         %Plug.Conn{} = piped_conn -&gt;
429           try do
430&gt;            plug.call(piped_conn, plug.init(opts))
431           else
432             conn -&gt;
433               measurements = %{duration: System.monotonic_time() - start}
434               metadata = %{metadata | conn: conn}
435               :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)

lib/todo_trek_web/endpoint.ex

1&gt;  defmodule TodoTrekWeb.Endpoint do
2     use Phoenix.Endpoint, otp_app: :todo_trek
3   
4     # The session will be stored in the cookie and signed,
5     # this means its contents can be read but not tampered with.
6     # Set :encryption_salt if you would also like to encrypt it.

deps/plug/lib/plug/debugger.ex

131             case conn do
132               %Plug.Conn{path_info: ["__plug__", "debugger", "action"], method: "POST"} -&gt;
133                 Plug.Debugger.run_action(conn)
134   
135               %Plug.Conn{} -&gt;
136&gt;                super(conn, opts)
137             end
138           rescue
139             e in Plug.Conn.WrapperError -&gt;
140               %{conn: conn, kind: kind, reason: reason, stack: stack} = e
141               Plug.Debugger.__catch__(conn, kind, reason, stack, @plug_debugger)

lib/todo_trek_web/endpoint.ex

1&gt;  defmodule TodoTrekWeb.Endpoint do
2     use Phoenix.Endpoint, otp_app: :todo_trek
3   
4     # The session will be stored in the cookie and signed,
5     # this means its contents can be read but not tampered with.
6     # Set :encryption_salt if you would also like to encrypt it.

lib/phoenix/endpoint/sync_code_reload_plug.ex

17   
18     def call(conn, {endpoint, opts}), do: do_call(conn, endpoint, opts, true)
19   
20     defp do_call(conn, endpoint, opts, retry?) do
21       try do
22&gt;        endpoint.call(conn, opts)
23       rescue
24         exception in [UndefinedFunctionError] -&gt;
25           case exception do
26             %UndefinedFunctionError{module: ^endpoint} when retry? -&gt;
27               # Sync with the code reloader and retry once

lib/plug/cowboy/handler.ex

6     def init(req, {plug, opts}) do
7       conn = @connection.conn(req)
8   
9       try do
10         conn
11&gt;        |&gt; plug.call(opts)
12         |&gt; maybe_send(plug)
13         |&gt; case do
14           %Plug.Conn{adapter: {@connection, %{upgrade: {:websocket, websocket_args}} = req}} = conn -&gt;
15             {handler, state, cowboy_opts} = websocket_args
16             {__MODULE__, copy_resp_headers(conn, req), {handler, state}, cowboy_opts}

/Users/joep/Repositories/cursussen/todo_trek/deps/cowboy/src/cowboy_handler.erl

32   -optional_callbacks([terminate/3]).
33   
34   -spec execute(Req, Env) -&gt; {ok, Req, Env}
35   	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
36   execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) -&gt;
37&gt;  	try Handler:init(Req, HandlerOpts) of
38   		{ok, Req2, State} -&gt;
39   			Result = terminate(normal, Req2, State, Handler),
40   			{ok, Req2, Env#{result =&gt; Result}};
41   		{Mod, Req2, State} -&gt;
42   			Mod:upgrade(Req2, Env, Handler, State);

/Users/joep/Repositories/cursussen/todo_trek/deps/cowboy/src/cowboy_stream_h.erl

301   	end.
302   
303   execute(_, _, []) -&gt;
304   	ok;
305   execute(Req, Env, [Middleware|Tail]) -&gt;
306&gt;  	case Middleware:execute(Req, Env) of
307   		{ok, Req2, Env2} -&gt;
308   			execute(Req2, Env2, Tail);
309   		{suspend, Module, Function, Args} -&gt;
310   			proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);
311   		{stop, _Req2} -&gt;

/Users/joep/Repositories/cursussen/todo_trek/deps/cowboy/src/cowboy_stream_h.erl

290   %% to simplify the debugging of errors. The proc_lib library
291   %% already adds the stacktrace to other types of exceptions.
292   -spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -&gt; ok.
293   request_process(Req, Env, Middlewares) -&gt;
294   	try
295&gt;  		execute(Req, Env, Middlewares)
296   	catch
297   		exit:Reason={shutdown, _}:Stacktrace -&gt;
298   			erlang:raise(exit, Reason, Stacktrace);
299   		exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown -&gt;
300   			erlang:raise(exit, {Reason, Stacktrace}, Stacktrace)

proc_lib.erl

No code available.

Connection details

Params

%{}

Request info

Headers

  • accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7
  • accept-encoding: gzip, deflate, br
  • accept-language: en-US,en;q=0.9
  • cache-control: max-age=0
  • connection: keep-alive
  • cookie: locale=en; _todo_trek_key=SFMyNTY.g3QAAAADbQAAAA5saXZlX3NvY2tldF9pZG0AAAA7dXNlcnNfc2Vzc2lvbnM6MERudHFGRE4xdlU1VXp0RWNDWGNodzJ2UERESXA2Q3Fka2NWTnpCUDdDVT1tAAAADXBob2VuaXhfZmxhc2h0AAAAAW0AAAAEaW5mb20AAAAdQWNjb3VudCBjcmVhdGVkIHN1Y2Nlc3NmdWxseSFtAAAACnVzZXJfdG9rZW5tAAAAINA57ahQzdb1OVM7RHAl3IcNrzwwyKegqnZHFTcwT-wl.CiW12cmteDAEIdcYXEiMXPXw5T6yZIqJyZdGO1n9OQU
  • dnt: 1
  • host: localhost:4000
  • referer: http://localhost:4000/users/register
  • sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
  • sec-ch-ua-mobile: ?0
  • sec-ch-ua-platform: "macOS"
  • sec-fetch-dest: document
  • sec-fetch-mode: navigate
  • sec-fetch-site: same-origin
  • sec-fetch-user: ?1
  • upgrade-insecure-requests: 1
  • user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36

Session

%{"live_socket_id" =&gt; "users_sessions:0DntqFDN1vU5UztEcCXchw2vPDDIp6CqdkcVNzBP7CU=", "phoenix_flash" =&gt; %{"info" =&gt; "Account created successfully!"}, "user_token" =&gt; &lt;&lt;208, 57, 237, 168, 80, 205, 214, 245, 57, 83, 59, 68, 112, 37, 220, 135, 13, 175, 60, 48, 200, 167, 160, 170, 118, 71, 21, 55, 48, 79, 236, 37&gt;&gt;}

Foreign key error when deleting a list

[debug] HANDLE EVENT "delete-list" in TodoTrekWeb.HomeLive
  Parameters: %{"id" => "4"}
[debug] QUERY OK source="lists" db=8.7ms queue=0.8ms idle=1712.3ms
SELECT l0."id", l0."title", l0."position", l0."user_id", l0."notifications", l0."inserted_at", l0."updated_at" FROM "lists" AS l0 WHERE (l0."user_id" = $1) AND (l0."id" = $2) [1, 4]
↳ TodoTrek.Todos.get_list!/2, at: lib/todo_trek/todos.ex:315
[debug] QUERY OK source="todos" db=0.5ms idle=1722.1ms
SELECT t0."id", t0."status", t0."title", t0."position", t0."list_id", t0."user_id", t0."inserted_at", t0."updated_at", t0."list_id" FROM "todos" AS t0 WHERE (t0."list_id" = $1) ORDER BY t0."list_id" [4]
↳ TodoTrekWeb.HomeLive.handle_event/3, at: lib/todo_trek_web/live/home_live.ex:148
[debug] QUERY OK db=0.1ms idle=1722.7ms
begin []
↳ TodoTrek.Todos.delete_list/2, at: lib/todo_trek/todos.ex:397
[debug] QUERY OK db=0.1ms
SELECT pg_advisory_xact_lock(8174290, 1) []
[debug] QUERY OK source="lists" db=0.6ms
UPDATE "lists" AS l0 SET "position" = l0."position" + -1 WHERE (l0."user_id" = $1) AND (l0."position" > (SELECT sl0."position" FROM "lists" AS sl0 WHERE (sl0."id" = $2))) [1, 4]
↳ TodoTrek.Todos.delete_list/2, at: lib/todo_trek/todos.ex:397
[debug] QUERY ERROR db=0.6ms
DELETE FROM "lists" WHERE "id" = $1 [4]
↳ TodoTrek.Todos.delete_list/2, at: lib/todo_trek/todos.ex:397
[debug] QUERY OK db=0.1ms
rollback []
↳ TodoTrek.Todos.delete_list/2, at: lib/todo_trek/todos.ex:397
[error] GenServer #PID<0.804.0> terminating
** (Ecto.ConstraintError) constraint error when attempting to delete struct:

    * "todos_list_id_fkey" (foreign_key_constraint)

If you would like to stop this constraint violation from raising an
exception and instead add it as an error to your changeset, please
call `foreign_key_constraint/3` on your changeset with the constraint
`:name` as an option.

The changeset has not defined any constraint.

Adding New Todo does not work

You can see it trying to create it but nothing goes through.
I have just changed the postgress password, run mix deps.get, mix ecto,create, mix ecto.migrate and ran the seed data...

[debug] QUERY OK db=2.8ms queue=0.4ms idle=99.0ms
INSERT INTO "activity_log_entries" ("action","after_text","before_text","list_id","meta","performer_text","subject_text","user_id","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) RETURNING "id" ["list_position_updated", "2", "2", 3, %{}, "[email protected]", "Social/Professional", 1, ~N[2023-08-10 21:34:50], ~N[2023-08-10 21:34:50]]
↳ TodoTrek.Todos.update_list_position/3, at: lib/todo_trek/todos.ex:39
[debug] Replied in 47ms
[debug] HANDLE EVENT "new" in TodoTrekWeb.HomeLive
  Component: TodoTrekWeb.TodoListComponent
  Parameters: %{"at" => -1, "list_id" => 3, "value" => ""}
[debug] Replied in 184µs
[debug] HANDLE EVENT "validate" in TodoTrekWeb.HomeLive
  Component: TodoTrekWeb.TodoListComponent
  Parameters: %{"_target" => ["todo", "title"], "todo" => %{"status" => "started", "title" => "T"}}
[debug] Replied in 245µs
[debug] HANDLE EVENT "validate" in TodoTrekWeb.HomeLive
  Component: TodoTrekWeb.TodoListComponent
  Parameters: %{"_target" => ["todo", "title"], "todo" => %{"status" => "started", "title" => "Te"}}
[debug] Replied in 186µs
[debug] HANDLE EVENT "validate" in TodoTrekWeb.HomeLive
  Component: TodoTrekWeb.TodoListComponent
  Parameters: %{"_target" => ["todo", "title"], "todo" => %{"status" => "started", "title" => "Tes"}}
[debug] Replied in 231µs
[debug] HANDLE EVENT "validate" in TodoTrekWeb.HomeLive
  Component: TodoTrekWeb.TodoListComponent
  Parameters: %{"_target" => ["todo", "title"], "todo" => %{"status" => "started", "title" => "Test"}}
[debug] Replied in 242µs
[debug] HANDLE EVENT "new" in TodoTrekWeb.HomeLive
  Component: TodoTrekWeb.TodoListComponent
  Parameters: %{"at" => -1, "list_id" => 3, "value" => ""}
[debug] Replied in 212µs

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.