The announcement came earlier this year. As someone who loved going to these three theaters, UA, Shattack Cinema, and California Cinema, in Berkeley throughout the ’90s and ’00s, this was heartbreaking.
I remember seeing Lord of the Rings: The Twin Towers at the UA after school, and it was magical. Something about not having these theaters for the next generation breaks my heart
I also distinctly going to see Spirited Away at Shattack Cinema, and remembering how Miyazaki movies instill magic into them.
I’ve put my dad’s 30 day notice for senior living in today. He’s ready to go home around Thanksgiving time. He’s ready to go him.
My dad’s health is generally okay. He’s having some skin problems at the moment from not putting on enough lotion. His hygiene is terrible, and we are wondering if he needs to shower every day. He also is not shaving. We had dim sum yesterday, and are ready for other caretakers to help take care of him.
His recovery is stagnant. We want him to interact with more of the people. I’m starting to create a recovery team for him so he has support
Back to your regular notes
I’m looking into a way to embed songs to Obsidian so I can link a song. I might use a YouTube embed in the meantime.
Here’s a note I wrote about my LYT showcase.
I recognize perfection is the enemy of good. And the way I’ve wanted to present my work is hardly how it looks when I’m in the middle of the idea. In my writing, first drafts hardly resemble the published draft. And even the published draft may be upcycled to other ideas. I’m very happy to see many other students embrace the mess
We are back in our regular rotation. LYT Workshop 12 wrapped last week, as I mentioned in the previous week, so expect this to be more regular.
I’m finally getting back to doing a quarterly review, a month later than I would’ve hoped.
NaNoWriMo is starting this Wednesday, and I’m planning to participate. I put my Project Page up for everyone to see.
At work, we’ve been chugging along working on next product requirements that’s hush hush here
As some people are aware with my father, we’ve followed up with PT this past week where he can start walking supervised without his cane, which is a win all around.
I’ve been returning to this idea about systems, and when I encounter new ones (tagged: experimental), I try to incorporate them in my own workflows and see if they mesh. Most do (I’m looking at you, GTD)
Missed last two weeks, as I’m going through the LYT workshop. I decided to cease publishing anything until I complete it.
Now that it’s the last week, I’m turning my attention back from my PKM to this website again. My goal is to publish once a week. 🤞🏼
Therapy Remark
Share Your Calm
This really resonated with me when talking with my therapist. In times of others’ stress, it’s easy to get caught up in the moment and take that on yourself. Instead, take a moment and show your calmness, rather than echoing the stress.
Freewriting sessions
When I kept up my journaling experiences for a decade, some days, I’d let it all out on paper. Take my thoughts and feelings and let them bleed on paper. Sometimes I’d come up with barely anything. Other times, I’d pour my heart on the page.
I’m going to return to this practice because it’s a form of practicing my calm (tying the previous point back in). I’ll take a short amount of time, 3 minutes to be exact, and do the work in my journal.
Filed under “Challenge what you believe”, this was kind of an eye opener. Sometimes, I want to say I’ve read the evidence and find it very compelling. But I didn’t. I was in a staff development meeting when I was teaching at a Prep school, and we had to work in groups telling each other what learning styles we were.
I’m back from the very last Strangeloop and from visiting my cousin for her baby shower in Las Vegas. I forgot how it feels to do non-stop traveling back-to-back. Reminds me of the time I traveled across the US, then hopped on a plane to South Korea.
I took this idea of “Weekly Notes” from Jamie Tanna who I met at Strangeloop, and I thought this would be a wonderful recurring segment for the blog. Even if I have low readership, this is a nice capsule to look at for my monthly, quarterly, and annual reviews. 😁
Derek Sivers updated his post on Tech Independence, where it’s a single command now
DALL·E 3 - [[OpenAI]] updated DALLE where prompt engineering is needed much less. Positioning is a lot better, with context
There’s a new map style on OpenStreetMap.org! The Tracestrack Topo map from @tracestrack.
I’ve decided to share things that I’ve found throughout the week, curating for you. You will find things that have caught my attention, notes that I think are worth exploring, and thoughts that have been perculating.
Now the list is there for us to add to, revise, and to refer to! He feels more supported and capable in being supportive and actually helping and I feel more supported and, less depressed!
@sharon.a.life
Now the list is there for us to add to, revise, and to refer to! He feels more supported and capable in being supportive and actually helping and I feel more supported and, less depressed!
♬ original sound - Sharon.a.life
Get used to the bear behind you.
— Werner Herzog’s 24th and final maxim
I heard this quote on a podcast in respect to creating. While there are things you can’t control, it’s your attitude to it that matters the most.
Also, Justin Welsh has written enough where his “new” content is really an update to his old content. Hence, having a 730-day content library. I would love to aspire on my ideas like this, and refine, refine, refine.
My dad fell from the roof. It was 10pm, Friday night, July 7th, 2023. It’s the call you never want to receive. I was getting ready for the dog’s last walk, and then go to bed. Instead, I rushed over to Highland Hospital in Oakland.
Initially, it all seemed the head injury he sustained was minor. He had a concussion and some bruising, which did not appear to be a big deal. Matters got out of hand on the second day when the nurse treated the family as if he knew better than the doctors. The nurse forced my dad a standing position, even as he complained of naseua and dizziness. There was a lot of confusion, and the nurse thought he knew better than to ask the doctor if my dad needed another CT scan ordered.
The next day, I got a call from the night nurse that the staff rushed my dad into the operating room. A surgeon performed a craniotomy on my dad. Within 24 hours, they stabilized his intercranial pressure and removed excess blood that pushed his brain to one side. It was a life or death situation, and we were all praying for the best outcome. He was intubated post-operation until the next day. Mentally, he appeared barely existing for the next few days. After being in the ICU that week, he was moved to a step-down unit, a section of the hospital that is less intense than the ICU, where he was under routine supervision for the next week.
Reflecting on the situation, I took a few things away that many care-givers should know.
Don’t take the nurse’s or doctor’s word. If someone you love is in critical care, you are the first observer, especially if you are there 24/7. Having family members help in shifts are really important, as you can cover as much time as possible.
Seek out the patient advocate. If things are not going well, like the nurse has become combative, every hospital has a patient advocate. It may take time to dig up their information, but having someone who truly cares for patient safety can be the difference between life and death.
Don’t be afraid to ask questions. Although I might be an outlier in the amount of medical knowledge I have, I still ask questions, even qualifying ones. It’s important to know which medications are administered and why. It’s important to know when the nurse shift change is. It’s important to know when your loved one will be discharged. Much of what I’ve learned in the last month has helped me navigate what shouldn’t be such a complex healthcare system.
When writing this, I assume the following.
The critical care facility is in the U.S. (California)
Your loved one is in a life or death situation
You have others helping you (makes it a lot easier)
I hope I’ll never have to come back to this note, but in the worst case scenario, I would have loved to read this advice.
Every weekend, I’ve been slowly moving to San Jose, so I haven’t been able to spend enough time with my hobbies and side projects. This website has seen a down tick in content, and I’m looking forward to writing more in the near future. Certainly post-move.
To spend the in-between time not thinking about code, I’ve been playing Zelda: Tears of the Kingdom. We recently finished Cult of the Lamb, and it felt pretty special.
I went to the Figma conference last week at San Francisco Moscone Center, and it was a packed event. I’m excited to dive deep into more UX work.
In the late 2000s, Google Reader was my jam. The feeds were just beginning to
populate, and I remember enjoying my social feed from MySpace and early Facebook.
What I didn’t realize was how much I would miss the RSS feed aggregator once it
was killed.
But why was Google Reader killed? The Verge wrote an excellent article going over the inception and ultimate demise of the website. It reminds me of the website, KilledByGoogle which chronicles over 200 products Google killed since its inception.
I was one of those die-hard Google Reader users. Once I caught wind of it, I immediately exported my data. I tried alternatives like Feedly and The Old Reader, but it didn’t feel the same.
There were serious limitations, like limited subscriptions, unless I paid for the premium versions. And that leads me to a much more serious problem that I was facing: over-consumption.
The Feeds Take Over
I think I’ve been faced with this information over-consumption more times than I
can count. If Google Reader started my addiction to short-lived information, then
social media certainly made things much worse. My mind was craving the constant
attention of an infinite scroll — a feed that will never stop. With Google Reader,
at least I could control the RSS feeds that were coming in. I remember one time,
I wanted to be informed by the world news events so I subscribed to BBC,
but it backfired quickly to give me a deluge of distractable information. Facebook
and Twitter feeds were no better. They started innocently but quickly turned
into what an algorithm wants me to read. And that loss of control quickly turned me
off.
In more recent years, I thought I could substitute this with email newsletters.
What I quickly learned was email is no substitute for a personal feed. I was overwhelmed with 50, then 100, then 1000 unread messages. There were newsletter
articles I was extremely interested in reading combined with many others I had no
interest in. On top of that, when I needed to reply to an email, I’d be
distracted by something I needed to read.
I’ve heard Cal Newport call this the “Hyperactive Hive Mind” in his book A World Without Email.
What I’ve come to interpret this as is a constant need to be distracted without an
off switch. Every single social media with a feed has given me this same problem.
I remember the top three subreddits I would mindlessly scroll, the YouTube feed
with an endless “Watch Later” playlist, and the email feed with newsletters left
unread. What it always ended with was going cold turkey. Uninstall Reddit. Place
time limits on YouTube. Stop reviewing emails for weeks at a time. It wasn’t a permanent solution.
Confessions of an Information Hoarder
Another thing Google Reader started me off with is information hoarding. Like copying a whole article I want to read to make notes and a summary about it later, but never returning to it. My Evernote became cluttered with
many unrealized things in life I wanted to experience, but can’t do because there’s
limited time. I’m reminded of tsundoku, a Japanese word that means “to pile up”.
I have a pile of books that are left unread for the moment I can pick one up and
start reading. For the pile of articles, the stack is so high, it’s a chore to go
through and just find the articles I want to read. More time is spent
sorting through the ones I know I should read.
Prioritization has never been a strong suit. I tend to spread out my efforts across
multiple projects rather than focus on one. With reading, I’m the same way. I
constantly have 2 or 3 books I’m reading concurrently. With articles, forget them.
I have found when I rely solely on a feed to make decisions for me, I waste the
most time on it.
The Rise of the “Slow Feed”
I talked at length about these problems, and minor band-aids on how I’ve fixed it.
At the root of it, the problem is my behavior interacting with the feed. Unless the feed is short and filtered down, I’ll spend more time in it than what I think is normal (but hey, what is normal anyway?). So I propose the “Slow Feed”.
While I can’t stop myself from browsing feeds, there is a limit on how much
I can save for later. The “Slow Feed” has a hard limit of 15 or fewer items, taking
a lesson from the quick line at the grocery store. If I can’t through this feed,
I can’t add more items. It means I must be very picky and choosy with what I consume.
I’m not didactic about the other feed though. To make it to the “Slow Feed”, it
must be an article or video I want to “save it later”, go into my Readwise Reader feed, then go into my shortlist, which serves as my
“Slow Feed”. I get the best of both worlds. I limit the time spent in the mindless
scrolling portion (the endless feeds) and make time to read the longer articles.
It’s like curing myself of FOMO, without really curing it.
Got any ideas on how to improve this? Email me and let me know!
Errata
I read The Information Diet by Clay Johnson years ago
and may be time to scan it over and see if I can find other ways to improve this.
The book that prompted me to read more consciously is Chris Bailey’s book,
How to Calm Your Mind, which has a section about
anxiety in the age of information overload. As part of my “Year of Intentions”,
this is one of the things I’m working on.
The 7 GUIs is a benchmark for comparing different GUI frameworks, proposed by Eugen Kiss. See his explanation below.
There are countless GUI toolkits in different languages and with diverse approaches to GUI development.
Yet, diligent comparisons between them are rare.
Whereas in a traditional benchmark competing implementations are compared in terms of their resource consumption,
here implementations are compared in terms of their notation.
To that end, 7GUIs defines seven tasks that represent typical challenges in GUI programming.
In addition, 7GUIs provides a recommended set of evaluation dimensions.
— Eugen Kiss
I’m going to walkthrough each GUI using Svelte, and annotate the code.
Counter
The task is to build a frame containing a label or read-only textfield T and a button B.
Initially, the value in T is “0” and each click of B increases the value in T by one.
<script> // Initialize the counter with "0" (as it says in the spec) let count = 0;</script><!-- Display the count, as a number input --><input type="number" bind:value={count}/><!-- Add a button that will increment the counter by 1 with each click. --><button on:click={() => (count += 1)}>count</button>
Temperature
The task is to build a frame containing two textfields TC and
TF representing the temperature in Celsius and Fahrenheit,
respectively. Initially, both TC and TF are empty. When
the user enters a numerical value into TC the corresponding value
in TF is automatically updated and vice versa. When the user enters
a non-numerical string into TC the value in TF is not
updated and vice versa. The formula for converting a temperature C in Celsius
into a temperature F in Fahrenheit is C = (F - 32) * (5/9) and the dual
direction is F = C * (9/5) + 32.
<script lang="ts"> // Initialize the values of celsius and fahrenheit let c = 20; let f = 68; // Given the value from Celsius, update Fahrenheit function setBothFromC(value: number): void { // The + is to convert the string to a number c = +value; // Use the formula from the spec to update fahrenheit f = +(32 + (9 / 5) * c).toFixed(1); } // Given the value from Fahrenheit, update Celsius function setBothFromF(value: number): void { f = +value; // Use the formula from the spec to update celsius c = +((5 / 9) * (f - 32)).toFixed(1); }</script><!--Add two different inputs. Since the inputs are two-way bound by the values,`c` and `f`, we can add an event listener to run the function to convert theother value.--><input value={c} on:input={(e) => setBothFromC(e.target.value)} type="number"/>°C =<input value={f} on:input={(e) => setBothFromF(e.target.value)} type="number"/>°F
Flight Booker
The task is to build a frame containing a form with three textfields
The task is to build a frame containing a combobox C with the two options
“one-way flight” and “return flight”, two textfields T1 and
T2 representing the start and return date, respectively, and a
button B for submitting the selected flight. T2 is enabled iff C’s
value is “return flight”. When C has the value “return flight” and
T2’s date is strictly before T1’s then B is disabled.
When a non-disabled textfield T has an ill-formatted date then T is colored
red and B is disabled. When clicking B a message is displayed informing the
user of his selection (e.g. “You have booked a one-way flight on
04.04.2014.”). Initially, C has the value “one-way flight” and T1
as well as T2 have the same (arbitrary) date (it is implied that
T2 is disabled).
<script> const DAY_IN_MS = 86400000 const tomorrow = new Date(Date.now() + DAY_IN_MS); // Create an array of year, month, day, in this format: YYYY-MM-DD let start = [ tomorrow.getFullYear(), pad(tomorrow.getMonth() + 1, 2), pad(tomorrow.getDate(), 2), ].join("-"); // our reactive variables let end = start; let isReturn = false; // Running statements reactively, updating the variables when they are changed $: startDate = convertToDate(start); $: endDate = convertToDate(end); // Click handler for the button function bookFlight() { // Determine type of return const type = isReturn ? "return" : "one-way"; let message = `You have booked a ${type} flight, leaving ${startDate.toDateString()}`; if (type === "return") { message += ` and returning ${endDate.toDateString()}`; } alert(message); } // Convert a string in the format YYYY-MM-DD to a Date object function convertToDate(str) { const split = str.split("-"); return new Date(+split[0], +split[1] - 1, +split[2]); } // Pad a number with leading zeros function pad(x, len) { x = String(x); while (x.length < len) x = `0${x}`; return x; }</script><!-- Create your select input for one-way or return flight option --><select bind:value={isReturn}> <option value={false}>one-way flight</option> <option value={true}>return flight</option></select><!-- Bind the inputs --><input type="date" bind:value={start} /><input type="date" bind:value={end} disabled={!isReturn} /><!-- Attempt to book flight --><button on:click={bookFlight} disabled={isReturn && startDate >= endDate} >book</button>
Timer
The task is to build a frame containing a gauge G for the elapsed time e, a
label which shows the elapsed time as a numerical value, a slider S by which
the duration d of the timer can be adjusted while the timer is running and a
reset button R. Adjusting S must immediately reflect on d and not only when S
is released. It follows that while moving S the filled amount of G will
(usually) change immediately. When e ≥ d is true then the timer stops (and G
will be full). If, thereafter, d is increased such that d > e will be true
then the timer restarts to tick until e ≥ d is true again. Clicking R will
reset e to zero.
<script> // It turns out, you can't run this in Astro without saying this component is client only import { onDestroy } from "svelte"; // Start elapsed at 0 milliseconds let elapsed = 0; // Set the range input to be 5 seconds / 5000 milliseconds let duration = 5000; let last_time = window.performance.now(); let frame; // IIFE for animation loop (function update() { // pass in the function for the animation frame (infinite looping) frame = requestAnimationFrame(update); // performance.now() is like Date.now(), but more accurate to tenths of a milliseconds const time = window.performance.now(); // Take the minimum of the time elapsed and add it to the new elapsed time elapsed += Math.min(time - last_time, duration - elapsed); last_time = time; })(); // When the component is destroyed, cancel the animation frame onDestroy(() => { cancelAnimationFrame(frame); });</script><!-- Create the label and use the progress tag to show the time elapsed vs duration --><label> elapsed time: <progress value={elapsed / duration} /></label><div>{(elapsed / 1000).toFixed(1)}s</div><label> duration: <!-- Bind the input to the duration. Max 20 seconds --> <input type="range" bind:value={duration} min="1" max="20000" /></label><!-- Allow the user to reset the timer --><button on:click={() => (elapsed = 0)}>reset</button>
CRUD
The task is to build a frame containing the following elements: a textfield
Tprefix, a pair of textfields Tname and
Tsurname, a listbox L, buttons BC, BU and
BD and the three labels as seen in the screenshot. L presents a
view of the data in the database that consists of a list of names. At most one
entry can be selected in L at a time. By entering a string into
Tprefix the user can filter the names whose surname start with the
entered prefix—this should happen immediately without having to submit the
prefix with enter. Clicking BC will append the resulting name from
concatenating the strings in Tname and Tsurname to L.
BU and BD are enabled if an entry in L is selected. In
contrast to BC, BU will not append the resulting name
but instead replace the selected entry with the new name. BD will
remove the selected entry. The layout is to be done like suggested in the
screenshot. In particular, L must occupy all the remaining space.
<script> // Have some people to start with let people = [ { first: "Hans", last: "Emil" }, { first: "Max", last: "Mustermann" }, { first: "Roman", last: "Tisch" }, ]; // Initialize the bound variables let prefix = ""; let first = ""; let last = ""; // Initialize the selected item index let i = 0; // Reactive statements when the changes $: filteredPeople = prefix ? people.filter((person) => { const name = `${person.last}, ${person.first}`; // Filter based off first or last name return name.toLowerCase().startsWith(prefix.toLowerCase()); }) : people; // Reactively change the selected when filtered people $: selected = filteredPeople[i]; // Reset all inputs when new selection made $: reset_inputs(selected); // Create a new person function create() { people = people.concat({ first, last }); i = people.length - 1; first = last = ""; } // Update the selected person function update() { selected.first = first; selected.last = last; people = people; } // Remove the selected person function remove() { // Remove selected person from the source array (people), not the filtered array const index = people.indexOf(selected); people = [...people.slice(0, index), ...people.slice(index + 1)]; first = last = ""; i = Math.min(i, filteredPeople.length - 2); } // Reset the input for first and last names function reset_inputs(person) { first = person ? person.first : ""; last = person ? person.last : ""; }</script><input placeholder="filter prefix" bind:value={prefix}/><select bind:value={i} size={5}> <!-- Loop through the filtered people --> {#each filteredPeople as person, i} <option value={i}>{person.last}, {person.first}</option> {/each}</select><!-- Create inputs for first and last names --><label ><input bind:value={first} placeholder="first" /></label><label ><input bind:value={last} placeholder="last" /></label><!-- CRUD operators --><div class="buttons"> <button on:click={create} disabled={!first || !last}>create</button> <button on:click={update} disabled={!first || !last || !selected} >update</button > <button on:click={remove} disabled={!selected}>delete</button></div>
Circle Drawer
The task is to build a frame containing an undo and redo button as well as a
canvas area underneath. Left-clicking inside an empty area inside the canvas
will create an unfilled circle with a fixed diameter whose center is the
left-clicked point. The circle nearest to the mouse pointer such that the
distance from its center to the pointer is less than its radius, if it exists,
is filled with the color gray. The gray circle is the selected circle C.
Right-clicking C will make a popup menu appear with one entry “Adjust
diameter…”. Clicking on this entry will open another frame with a slider
inside that adjusts the diameter of C. Changes are applied immediately.
Closing this frame will mark the last diameter as significant for the
undo/redo history. Clicking undo will undo the last significant change (i.e.
circle creation or diameter adjustment). Clicking redo will reapply the last
undoed change unless new changes were made by the user in the meantime.
<script> // Initialize the bound variables let i = 0; let undoStack = [[]]; let circles = []; let selected; let adjusting = false; let adjusted = false; // On handling click, create circle with default radius 50 px function handleClick(event) { if (adjusting) { adjusting = false; // if circle was adjusted, // push to the stack if (adjusted) push(); return; } const circle = { cx: event.clientX, cy: event.clientY, r: 50, }; // Add circles to list of circles. The selected circle is the current circle circles = circles.concat(circle); selected = circle; push(); } function adjust(event) { selected.r = +event.target.value; circles = circles; adjusted = true; } function select(circle, event) { if (!adjusting) { event.stopPropagation(); selected = circle; } } // Use a stack for keeping track of the circles function push() { const newUndoStack = undoStack.slice(0, ++i); newUndoStack.push(clone(circles)); undoStack = newUndoStack; } function travel(d) { circles = clone(undoStack[(i += d)]); adjusting = false; } function clone(circles) { return circles.map(({ cx, cy, r }) => ({ cx, cy, r })); }</script><!-- Put in the buttons for controls --><div class="controls"> <button on:click={() => travel(-1)} disabled={i === 0}>undo</button> <button on:click={() => travel(+1)} disabled={i === undoStack.length - 1} >redo</button ></div><!-- Draw with an SVG. Bind the click handler --><!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions --><svg on:click={handleClick}> <!-- Draw all of the circles --> {#each circles as circle} <!-- svelte-ignore a11y-click-events-have-key-events --> <circle cx={circle.cx} cy={circle.cy} r={circle.r} on:click={(event) => select(circle, event)} on:contextmenu|stopPropagation|preventDefault={() => { // When right-clicking, open the adjuster adjusting = !adjusting; if (adjusting) selected = circle; }} fill={circle === selected ? "#ccc" : "white"} /> {/each}</svg><!-- Show the adjuster if adjusting a circle's size -->{#if adjusting} <div class="adjuster"> <p>adjust diameter of circle at {selected.cx}, {selected.cy}</p> <input type="range" value={selected.r} on:input={adjust} /> </div>{/if}
Cells
The task is to create a simple but usable spreadsheet application. The
spreadsheet should be scrollable. The rows should be numbered from 0 to 99 and
the columns from A to Z. Double-clicking a cell C lets the user change C’s
formula. After having finished editing the formula is parsed and evaluated and
its updated value is shown in C. In addition, all cells which depend on C must
be reevaluated. This process repeats until there are no more changes in the
values of any cell (change propagation). Note that one should not just
recompute the value of every cell but only of those cells that depend on
another cell’s changed value. If there is an already provided spreadsheet
widget it should not be used. Instead, another similar widget (like JTable in
Swing) should be customized to become a reusable spreadsheet widget.
This one isn’t in the Svelte documentation, so I found a different
implementation that went through it perfectly. Link
Cells is split up into two Svelte components: Cell and Cells.
<!-- Cell.svelte --><script> // Initialized props export let j; export let i; export let focused; export let data; export let p; export let handleFocus; export let handleBlur; export let handleKeydown; export let handleInput; // Keep track of the current key let key = j + i; // Keep track if a cell is focused let hasFocus = false; $: if (focused === key && !hasFocus) { hasFocus = true; } else if (focused !== key && hasFocus) { hasFocus = false; }</script><!-- When focused, change the cell into an input --><!-- Otherwise parse the formula -->{#if hasFocus} <input id={"input-" + key} value={$data[key] || ""} autofocus on:focus={() => handleFocus(key)} on:blur={() => handleBlur(key)} on:keydown={(e) => handleKeydown(e, j, i)} on:input={(e) => handleInput(e, key)} />{:else} <div>{p.parse($data[key]) || ""}</div>{/if}
<!-- Cells.svelte --><script> import Cell from "./Cell.svelte"; import { data } from "./store.js"; import { sampleData } from "./sampleData.js"; import { Parser } from "./parse.js"; // Initialize with the sample data set data.set(sampleData); // Create 26 columns w/ the letters of the alphabet const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // Max 100 x 100 cells export let shape = [100, 100]; const rows = range(shape[1]); const columns = letterRange(shape[0]); const p = new Parser(data, columns, rows); let focused; let tBody; // Create range array function range(n) { return [...Array(n).keys()]; } // Create letter range function letterRange(n) { return range(n).map(getNumberAsLetters); } // Loop through letters function getBase26(n) { let result = []; while (n > 25) { let remainder = n % 26; result.push(remainder); n = Math.floor(n / 26) - 1; } result.push(n); return result.reverse(); } // Get the letter range and join them function getNumberAsLetters(n) { let arr = getBase26(n); return arr.map((num) => LETTERS[num]).join(""); } function handleFocus(key) { if (focused !== key) { $data[key] = $data[key] || ""; focused = key; setTimeout(() => { let target = tBody.querySelector("#input-" + key); if (target) { target.focus(); target.setSelectionRange(0, 9999); } }, 10); } } function handleBlur(key) { if (focused === key) focused = undefined; } function handleInput(e, key) { $data[key] = e.target.value; } function handleKeydown(e, column, row) { // Navigate across the spreadsheet with arrow keys (and alt/option key) let selector; if (e.key === "ArrowUp") { let newRow = findAdjacent(rows, row, "before"); selector = newRow !== null ? column + newRow : null; } if (e.key === "ArrowDown" || e.key === "Enter") { let newRow = findAdjacent(rows, row, "after"); selector = newRow !== null ? column + newRow : null; } if (e.key === "ArrowLeft" && e.altKey) { let newColumn = findAdjacent(columns, column, "before"); selector = newColumn !== null ? newColumn + row : null; } if (e.key === "ArrowRight" && e.altKey) { let newColumn = findAdjacent(columns, column, "after"); selector = newColumn !== null ? newColumn + row : null; } if (selector) { e.preventDefault(); handleFocus(selector); } } function findAdjacent(arr, value, direction) { let index = arr.indexOf(value); if (index === -1) return null; if (direction === "before") return arr[index - 1] === undefined ? null : arr[index - 1]; if (direction === "after") return arr[index + 1] || null; return null; } function clear() { data.set({}); }</script><div class="wrapper"> <table> <thead> <tr> <td class="row-key" /> {#each columns as column} <td class="column-key">{column}</td> {/each} </tr> </thead> <tbody bind:this={tBody}> {#each rows as i} <tr id={"row-" + i}> <td class="row-key">{i}</td> {#each columns as j} <td id={j + i} on:click={() => handleFocus(j + i)}> <Cell {j} {i} {focused} {data} {p} {handleFocus} {handleBlur} {handleKeydown} {handleInput} /> </td> {/each} </tr> {/each} </tbody> </table></div><button on:click={clear}>Clear</button>
There are two utility functions to help out the operations: parse and store
(the latter being the Svelte store to save in local state).
// parse.jsexport class Parser { constructor(store, columns, rows) { this.cells = {} this.store = store this.columns = columns this.rows = rows this.operations = { sum: (a, b) => a + b, sub: (a, b) => a - b, mul: (a, b) => a * b, div: (a, b) => a / b, mod: (a, b) => a % b, exp: (a, b) => a ** b } // subscribe to store this.store.subscribe(value => { this.cells = value }) } cartesianProduct(letters, numbers) { var result = [] letters.forEach(letter => { numbers.forEach(number => { result.push(letter + number) }) }) return result } findArrRange(arr, start, end) { let startI = arr.indexOf(start) let endI = arr.indexOf(end) if (startI == -1 || endI == -1 || startI > endI) return [] return arr.slice(startI, endI + 1) } getRange(rangeStart, rangeEnd) { rangeStart = this.splitOperand(rangeStart) rangeEnd = this.splitOperand(rangeEnd) let letters = this.findArrRange(this.columns, rangeStart[0], rangeEnd[0]) let numbers = this.findArrRange(this.rows, rangeStart[1], rangeEnd[1]) return this.cartesianProduct(letters, numbers) } splitOperand(operand) { return [operand.match(/[a-zA-Z]+/)[0], Number(operand.match(/\d+/)[0])] } rangeOperation(op, rangeStart, rangeEnd) { if (!(this.isWellFormed(rangeStart) && this.isWellFormed(rangeEnd))) return this.originalString let range = this.getRange(rangeStart, rangeEnd) return range .map(address => Number(this.parse(this.cells[address]))) .reduce(this.operations[op]) } singleOperation(op, operand1, operand2) { let first = this.parseOperand(operand1) let second = this.parseOperand(operand2) if (first === null || second === null) return this.originalString return this.operations[op](first, second).toString() } isWellFormed(operand) { return /[a-zA-Z]+\d+/.test(operand) } parseOperand(operand) { if (!isNaN(Number(operand))) return Number(operand) if (operand in this.cells) return Number(this.parse(this.cells[operand])) if (this.isWellFormed(operand)) return 0 return null } parseOperation(op, formula) { if (!(formula.startsWith('(') && formula.endsWith(')'))) return this.originalString formula = formula.slice(1, formula.length - 1) let operationType let formulaArr if (formula.includes(',')) { operationType = 'single' formulaArr = formula.split(',') } else if (formula.includes(':')) { operationType = 'range' formulaArr = formula.split(':') } if (formulaArr.length !== 2) return this.originalString if (operationType === 'single') return this.singleOperation(op, formulaArr[0], formulaArr[1]) if (operationType === 'range') return this.rangeOperation(op, formulaArr[0], formulaArr[1]) return this.originalString } parse(str) { this.originalString = str if (typeof str !== 'string') return '' if (!str.startsWith('=')) return str let formula = str.slice(1) if (formula.slice(0, 3).toLowerCase() in this.operations) { return this.parseOperation( formula.slice(0, 3).toLowerCase(), formula.slice(3).toUpperCase() ) } else { return this.cells[formula] || str } }}
// store.jsimport { writable } from "svelte/store";export const data = writable({});
The last file is to load prefilled data, but we don’t need to go over that.
// An example of the sampleData fileexport let sampleData = { A0: "Data", A1: "20", A2: "15"};
SolidJS is a Javascript framework for building fast, declarative UIs on the web. It shares many ideas with React, but does not use the virtual DOM to deliver a more performant and pragmatic developer experience.
In the playground, you can view the compiled output.
Also, you can change the compile mode, between “Client side rendering”, “Server side rendering”, and “Client side rendering with hydration”
Any code that you write in the playground can be exported to a coding sandbox, like Codesandbox. So helpful!
Philosophy - Think Solid
Declarative Data
Vanishing Components
Solid updates are completely independent of the components. Component functions are called once and then cease to exist.
Read/Write segregation
We don’t need true immutability to enforce unidirectional flow, just the ability to make the conscious decision which consumers may write and which may not.
Simple is better than easy
Compilation
Solid’s JSX compiler doesn’t just compile JSX to JavaScript; it also extracts reactive values (which we’ll get to later in the tutorial) and makes things more efficient along the way.
This is more involved than React’s JSX compiler, but much less involved than something like Svelte’s compiler. Solid’s compiler doesn’t touch your JavaScript, only your JSX.
Destructuring props is usually a bad idea in Solid. Under the hood, Solid uses proxies to hook into props objects to know when a prop is accessed. When we destructure our props object in the function signature, we immediately access the object’s properties and lose reactivity.
So in general, avoid the following:
function Bookshelf({ name }) { return ( <div> <h1>{name}'s Bookshelf</h1> <Books /> <AddBook /> </div> );}
And replace with props instead.
Dependency Arrays
In React, you’d declare the dependencies explicitly using the dependency array. If you didn’t, the effect would rerun whenever any state in the component changes. In Solid, dependencies are tracked automatically, and you don’t have to worry about extra reruns.
Looping with array.map
If we used array.map in Solid, every element inside the book would have to rerender whenever the books signal changes. The For component checks the array when it changes, and only updates the necessary element. It’s the same kind of checking that React’s VDOM rendering system does for us when we use .map.
Conditional if statements on re-rendering
In the Building UI with Components section of this tutorial, we noted that component functions run only once in Solid. This means the JSX returned from that initial function return is the only JSX that will ever be returned from the function.
In Solid, if we want to conditionally display JSX in a component, we need that condition to reside within the returned JSX. While this takes some adjustment when coming from React, we have found that the fine-grained control afforded by Solid’s reactive system is worth the trade-off.
Reactivity and proxy objects
In Solid, props and stores are proxy objects that rely on property access for tracking and reactive updates. Watch out for destructuring or early property access, which can cause these properties to lose reactivity or trigger at the wrong time.
onChange vs. onInput
In React, onChange fires whenever an input field is modified, but this isn’t how onChangeworks natively. In Solid, use onInput to subscribe to each value change.
No VDOM or VDOM APIs
Finally, there is no VDOM so imperative VDOM APIs like React.Children and React.cloneElement have no equivalent in Solid. Instead of creating or modifying DOM elements directly, express your intentions declaratively.
Solid Primitives
Signals - The basic way to manage state in the application
Similar to useState in React, but as a reactive value
Derived state - you can track a computed state from a signal, which is also reactive
You can pass the signal as a prop like this: <BookList books={books()} />. It’s not a typo to use the function as it’s passing an accessor, which is important for reactivity.
Effects - ability to react to signal changes
A driving philosophy of Solid is that, by treating everything as a signal or an effect, we can better reason about our application.
Looping
Solid has a component called <For /> with an each prop. You can pass in a signal that will make this reactive.
As you can see, onInput is the event handler that takes in the event. In this case, we are setting the new book for each input (the title and author).
The onClick handler for the button uses the addBook function where it can prevent the form from submitting, set the books using a new array, then resetting the new book. It should be noted that setBooks is using a callback function where you access the current state. Also, it should be noted not to mutate state by creating that new array (much like in Redux practice).
The primitive for any external data source is createResource. The function returns a deconstructed array with the data. It takes two arguments: the signal and the data fetching function.
Putting it all together, query is the signal. searchBooks is the data fetching function. Once the data is returned, we can loop over it, and for each item, we can set the books if selected.
The following is a code example introducing how Reactivity or Reactive Programming works.
import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(2);const [multipler, setMultiplier] = createSignal(2);const product = () => count() * multipler();// Change the count every secondsetInterval(() => { setCount(count() + 1);}, 1000);// Change the multiplier every 2.5 secondssetInterval(() => { setCount(multipler() + 1);}, 2500);// Effect automatically detects when a signal has changed// So you don't have to add a dependency array.// This is defined as "reactivity"createEffect(() => { console.log(`${count()} * ${multiplier()} = ${product()}`);});
createSignal works by creating a data structure that can read and write. To each of these functions, subscribers are added and updated
CreateEffect works by executing on a context queue (JS array). It takes its queue, executes it, then pops it (at least in pseudocode)
SolidJS uses “granular updates” so only the variables that change only update the DOM, and not entire components.
In this example, we extracted Multipler into its own component, added props, like React, and called it multiple times in App. As you will also notice, signals do not have to be in the function scope! This is counter to what you do in React. Of course, if you don’t want to share the signal across other components, you can keep it in the functional lexical scope.
I started this journey into Typescript by taking a ton of notes with Github Copilot X, “Typescript in 50 Lessons”, and the official documentation. I was able to tailor my experience in understanding how to create types through this method. Without further ado, here’s some notes that I generated (and modified).
What is Typescript?
TypeScript is a superset of JavaScript that adds optional static typing and other features to the language. It is designed to make it easier to write and maintain large-scale JavaScript applications. TypeScript code is compiled into JavaScript code that can run in any browser or JavaScript runtime.
TypeScript provides features such as classes, interfaces, enums, and modules that are not available in standard JavaScript. It also includes support for modern JavaScript features such as async/await and decorators.
TypeScript is developed and maintained by Microsoft, and it is open source and free to use. It is widely used in web development, and many popular frameworks and libraries such as Angular, React, and Vue have TypeScript support.
Static Typing
Static typing in TypeScript allows you to specify the types of variables, function parameters, and return values. This means that you can catch type-related errors at compile-time rather than at runtime.
For example, you can specify that a variable is of type string, and TypeScript will give you an error if you try to assign a number to that variable. Similarly, you can specify that a function takes a parameter of type number, and TypeScript will give you an error if you try to call that function with a string.
Here’s an example of a function that takes two numbers and returns their sum, with static typing:
function addNumbers(x: number, y: number): number { return x + y;}
In this example, the x and y parameters are of type number, and the function returns a value of type number. If you try to call this function with non-numeric arguments, TypeScript will give you an error.
In addition, you can declare types for functions separately to the function implementation. This allows you to define a function type once and reuse it in multiple places.
The void type is used to indicate that a function does not return a value. You can use void as the return type of a function to explicitly indicate that the function does not return anything.
Here’s an example of a function that returns void:
function logMessage(message: string): void { console.log(message);}
In this example, the logMessage function takes a parameter of type string and logs it to the console. The function returns void, which means it does not return a value.
Type Assertions
A type assertion is a way to tell the compiler that you know more about the type of a value than it does. Type assertions are sometimes necessary when working with values that have a more general type than you need.
You can use a type assertion by adding the type you want to assert to the end of an expression, preceded by the as keyword. Here’s an example:
let myValue: any = "hello world";let myLength: number = (myValue as string).length;
In this example, the myValue variable is declared as type any, which means it can hold any value. We then use a type assertion to tell the compiler that we know myValue is actually a string, so we can access its length property.
You can also use angle bracket syntax (< >) to perform a type assertion:
let myValue: any = "hello world";let myLength: number = (<string>myValue).length;
In this example, the type assertion is performed using angle bracket syntax instead of the as keyword.
It’s important to note that type assertions do not change the runtime behavior of your code. They only affect the type checking performed by the TypeScript compiler.
You can use rest parameters to represent an indefinite number of arguments as an array. Rest parameters are denoted by an ellipsis (…) followed by the parameter name.
Here’s an example of a function that uses rest parameters:
function sum(...numbers: number[]): number { return numbers.reduce((total, num) => total + num, 0);}
Primitive Types
There are several primitive types that represent the most basic types of values. These primitive types include:
number: represents numeric values, including integers and floating-point numbers.
string: represents textual values, such as words and sentences.
boolean: represents logical values, either true or false.
null: represents the intentional absence of any object value.
undefined: represents the unintentional absence of any object value.
symbol: represents a unique identifier that can be used as an object property.
let age: number = 30;let name: string = "John";let isStudent: boolean = true;let favoriteColor: string | null = null;let phoneNumber: string | undefined = undefined;let id: symbol = Symbol("id");
Non-primitive types are types that are based on objects rather than simple values. These types include:
object: represents any non-primitive value, including arrays, functions, and objects.
array: represents an ordered list of values of a single type.
function: represents a callable object that can be invoked with arguments.
class: represents a blueprint for creating objects that have properties and methods.
interface: represents a contract that describes the shape of an object.
enum: represents a set of named constants.
Dynamically Generated Types
Dynamically generated types are types that are generated at runtime based on the shape of the data. There are several ways to generate types dynamically in TypeScript:
Index signatures: You can use index signatures to define an object type with dynamic keys. For example:
interface Dictionary<T> { [key: string]: T;}
This interface defines a dictionary type with a dynamic key of type string and a value of type T.
Type assertions: You can use type assertions to cast a value to a specific type at runtime.
const data = JSON.parse(jsonString) as MyType;
This code uses a type assertion to cast the parsed JSON data to a specific type called MyType.
Type guards: You can use type guards to check the type of a value at runtime and conditionally cast it to a specific type. For example:
function isPerson(obj: any): obj is Person { return "name" in obj && "age" in obj;}function printPerson(obj: any) { if (isPerson(obj)) { console.log(`Name: ${obj.name}, Age: ${obj.age}`); } else { console.log("Not a person"); }}
Intersection types: You can use intersection types to combine multiple types into one.
Union Types
A union type is a type that can represent values of multiple types. Union types are denoted by the | symbol between the types.
function printId(id: number | string) { console.log(`ID is ${id}`);}
Union types are useful when you want to write code that can handle multiple types of values.
Intersection Types
An intersection type is a type that combines multiple types into one. Intersection types are denoted by the & symbol between the types.
interface Named { name: string;}interface Loggable { log(): void;}function logName(obj: Named & Loggable) { console.log(`Name is ${obj.name}`); obj.log();}
Intersection types are useful when you want to write code that can handle objects with multiple sets of properties and methods.
Value Types
This interface definition ensures that this Event must have a kind property with one of the three specified values. This can help prevent errors and make the code more self-documenting.
You can use a type guard function to check the type of a value at runtime and conditionally cast it to a specific type. A type guard is a function that returns a boolean value and has a special obj is Type syntax that tells TypeScript that the value is of a specific type.
function isPerson(obj: any): obj is Person { return "name" in obj && "age" in obj;}
You can then use the type guard function to conditionally cast a value to a specific type. Here’s an example:
function printPerson(obj: any) { if (isPerson(obj)) { console.log(`Name: ${obj.name}, Age: ${obj.age}`); } else { console.log("Not a person"); }}
Type Assertions
Type assertions in TypeScript are a way to tell the compiler that you know more about the type of a value than it does. Type assertions are similar to type casting in other languages, but they don’t actually change the type of the value at runtime. Type assertions use the as keyword.
const myValue: any = "hello world";const myLength: number = (myValue as string).length;
This can be useful in JSX format too.
const myComponent = ( <MyComponent prop1={value1} prop2={value2} />) as JSX.Element;
Type Aliases
Type aliases in TypeScript are a way to create a new name for an existing type. They allow you to define a custom name for a type that may be complex or difficult to remember.
Type aliases can also be used with union types, intersection types, and other advanced type constructs. They can help make your code more readable and maintainable by giving complex types a simpler name.
Mapped Types
Mapped types in TypeScript are a way to create new types based on an existing type by transforming each property in the original type in a consistent way. Mapped types use the keyof keyword to iterate over the keys of an object type and apply a transformation to each key.
type Readonly<T> = { readonly [P in keyof T]: T[P];};interface Person { name: string; age: number;}const myPerson: Readonly<Person> = { name: 'John', age: 30 };myPerson.name = 'Jane'; // Error: Cannot assign to 'name' because it is a read-only property.
Mapped types can be used to create many other types, such as Partial, Pick, and Record. They are a powerful feature of TypeScript that can help make your code more expressive and maintainable.
Partial is a mapped type that creates a new type with all properties of the original type set to optional. Here’s an example:
A type predicate in TypeScript is a function that takes an argument and returns a boolean value indicating whether the argument is of a certain type. Type predicates are used to narrow the type of a variable or parameter based on a runtime check.
function isString(value: unknown): value is string { return typeof value === 'string';}function myFunc(value: unknown) { if (isString(value)) { // value is now of type string console.log(value.toUpperCase()); } else { console.log('Not a string'); }}
We define a function myFunc that takes an argument value of type unknown. We then use the isString function to check if value is of type string. If it is, we can safely call the toUpperCase method on value because TypeScript has narrowed the type to string.
never, undefined, null Types
never is a type that represents a value that will never occur. It is used to indicate that a function will not return normally, or that a variable will never have a certain value.
function throwError(message: string): never { throw new Error(message);}
undefined and null are both types and values. The undefined type represents a value that is not defined, while the null type represents a value that is explicitly set to null.
TypeScript also has a --strictNullChecks compiler option that can help prevent null and undefined errors. When this option is enabled, variables that are not explicitly set to null or undefined are considered to be of a non-nullable type. This means that you cannot assign null or undefined to these variables without first checking for their existence.
Classes
Classes in TypeScript are a way to define object-oriented programming (OOP) constructs. They allow you to define a blueprint for creating objects that have properties and methods.
Here’s an example of a class in TypeScript:
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } sayHello() { console.log( `Hello, my name is ${this.name} and I am ${this.age} years old.` ); }}
In this example, the Person class has two properties (name and age) and a method (sayHello). The constructor method is used to initialize the properties when a new object is created.
You can create a new Person object like this:
const person = new Person("Alice", 30);person.sayHello(); // logs "Hello, my name is Alice and I am 30 years old."
One of the main differences between TypeScript and JavaScript classes is that TypeScript allows you to specify the types of class properties, method parameters, and return values. This helps catch type-related errors at compile-time rather than at runtime.
Another difference is that TypeScript provides access modifiers such as public, private, and protected, which allow you to control the visibility of class members. This can help you write more secure and maintainable code.
Finally, TypeScript classes can implement interfaces, which are contracts that describe the shape of an object. This can help enforce type checking on objects that implement the interface.
Access modifiers in TypeScript classes are keywords that determine the visibility of class members (properties and methods). There are three access modifiers in TypeScript:
public: Public members are accessible from anywhere, both inside and outside the class.
private: Private members are only accessible from within the class. They cannot be accessed from outside the class, not even from derived classes.
protected: Protected members are accessible from within the class and from derived classes. They cannot be accessed from outside the class hierarchy.
Here’s an example of a class with access modifiers:
class Person { public name: string; private age: number; protected address: string; constructor(name: string, age: number, address: string) { this.name = name; this.age = age; this.address = address; } public sayHello() { console.log( `Hello, my name is ${this.name} and I am ${this.age} years old.` ); } private getAge() { return this.age; } protected getAddress() { return this.address; }}
In this example, the name property is public, so it can be accessed from anywhere. The age property is private, so it can only be accessed from within the Person class. The address property is protected, so it can be accessed from within the Person class and from derived classes.
Abstract Class
An abstract class is a class that cannot be instantiated directly. Instead, it is meant to be subclassed by other classes that provide concrete implementations of its abstract methods.
An abstract class can have both abstract and non-abstract methods. Abstract methods are declared without an implementation, and must be implemented by any concrete subclass. Non-abstract methods can have an implementation, and can be called by concrete subclasses.
Here’s an example of an abstract class in TypeScript:
abstract class Animal { abstract makeSound(): void; move(distanceInMeters: number) { console.log(`Animal moved ${distanceInMeters}m.`); }}class Dog extends Animal { makeSound() { console.log("Woof! Woof!"); }}const dog = new Dog();dog.makeSound(); // logs "Woof! Woof!"dog.move(10); // logs "Animal moved 10m."
In this example, the Animal class is an abstract class that defines an abstract makeSound method and a non-abstract move method. The Dog class is a concrete subclass of Animal that provides an implementation of the makeSound method. The Dog class can also call the move method inherited from Animal.
But then, what’s the difference between an abstract class and an interface? Take a look at the following example:
In this example, the Animal abstract class and the IAnimal interface both describe objects with a makeSound method and a move method. However, the Animal class is meant to be subclassed, while the IAnimal interface is meant to be implemented.
Some advantages of using inheritance instead of interfaces are:
Multiple Inheritance: An interface can be implemented by multiple classes, while an abstract class can only be subclassed by one class. This can help you create more flexible and reusable code.
Lighter Weight: An interface is a lighter weight construct than an abstract class, since it does not have any implementation details. This can help you write more modular and composable code.
Interfaces
In TypeScript, interfaces are contracts that describe the shape of an object. They define a set of properties and methods that an object must have in order to be considered an implementation of the interface.
Here’s an example of an interface in TypeScript:
interface Person { name: string; age: number; sayHello(): void;}
In this example, the Person interface has two properties (name and age) and a method (sayHello). Any object that implements the Person interface must have these properties and method.
You can use an interface to enforce type checking on objects that implement it. For example, you can define a function that takes a Person object as a parameter:
function greet(person: Person) { person.sayHello();}
In this example, the greet function takes a Person object as a parameter. TypeScript will give you an error if you try to call this function with an object that does not implement the Person interface.
While interfaces and classes have similar ways in defining object types, here are some differences:
Implementation: A class can have both properties and methods, while an interface can only have properties and method signatures. A class is an implementation of an object, while an interface is just a description of an object.
Instantiation: A class can be instantiated to create objects, while an interface cannot. An interface is just a contract that describes the shape of an object.
Inheritance: A class can inherit from another class or multiple classes, while an interface can only extend other interfaces.
Access Modifiers: A class can have access modifiers (public, private, protected) to control the visibility of its members, while an interface cannot.
The benefits of using an interface are the following:
Type checking: Interfaces allow you to enforce type checking on objects that implement them. This can help catch errors at compile-time rather than at runtime.
Code reuse: Interfaces can be used to define a common set of properties and methods that multiple objects can implement. This can help reduce code duplication and make your code more modular.
Abstraction: Interfaces can be used to abstract away implementation details and focus on the contract between objects. This can help make your code more maintainable and easier to reason about.
Polymorphism: Interfaces can be used to create polymorphic behavior, where different objects can be used interchangeably as long as they implement the same interface.
Polymorphism
Polymorphism is the ability of objects to take on multiple forms. In TypeScript, interfaces can be used to create polymorphic behavior, where different objects can be used interchangeably as long as they implement the same interface.
Here’s an example of polymorphism in TypeScript:
interface Shape { getArea(): number;}class Rectangle implements Shape { constructor(private width: number, private height: number) {} getArea() { return this.width * this.height; }}class Circle implements Shape { constructor(private radius: number) {} getArea() { return Math.PI * this.radius ** 2; }}function printArea(shape: Shape) { console.log(`The area of the shape is ${shape.getArea()}`);}const rectangle = new Rectangle(10, 20);const circle = new Circle(5);printArea(rectangle); // logs "The area of the shape is 200"printArea(circle); // logs "The area of the shape is 78.53981633974483"
In this example, the Shape interface defines a getArea method that returns a number. The Rectangle and Circle classes both implement the Shape interface, so they both have a getArea method. The printArea function takes a Shape object as a parameter, so it can be called with either a Rectangle or a Circle object. This is an example of polymorphism, where different objects can be used interchangeably as long as they implement the same interface.
Namespace
In TypeScript, a namespace is a way to group related code into a named scope. Namespaces can contain classes, interfaces, functions, and other code constructs.
You can define a namespace using the namespace keyword, and you can access its contents using the dot notation. Here’s an example:
In this example, the MyNamespace namespace contains an interface Person and a function greet. The Person interface is exported so that it can be used outside of the namespace. The greet function is also exported, and it takes a Person object as an argument.
To use the Person interface and the greet function, you can access them using the dot notation with the namespace name (MyNamespace.Person and MyNamespace.greet).
Enums
In TypeScript, an enum is a way to define a set of named constants. Enums are useful when you have a fixed set of values that a variable can take on, such as the days of the week or the colors of the rainbow.
Here’s an example of an enum in TypeScript:
enum Color { Red, Green, Blue,}let myColor: Color = Color.Red;console.log(myColor); // logs 0
In this example, the Color enum defines three named constants (Red, Green, and Blue). Each constant is assigned a numeric value (0, 1, and 2, respectively) by default. You can also assign string or numeric values explicitly:
enum Color { Red = "#ff0000", Green = "#00ff00", Blue = "#0000ff",}
Generics
Generics in TypeScript allow you to create reusable code components that can work with different types. They provide a way to define functions, classes, and interfaces that can work with a variety of data types, without having to write the same code multiple times.
Here’s an example of a generic function that takes an array of any type and returns the first element:
function getFirstElement<T>(arr: T[]): T { return arr[0];}
In this example, the syntax indicates that the function is generic and can work with any type. The T is a placeholder for the actual type that will be used when the function is called.
The arr parameter is an array of type T, and the function returns a value of type T. When the function is called, the actual type for T is inferred from the type of the array that is passed in.
Generics in TypeScript are commonly used in situations where you want to write code that can work with multiple types. Here are some common use cases for generics:
Collections: Generics can be used to create collections that can hold any type of data. For example, you can create a generic List class that can hold a list of any type of data.
Functions: Generics can be used to create functions that can work with any type of data. For example, you can create a generic identity function that returns its argument of type T.
Interfaces: Generics can be used to create interfaces that can work with any type of data. For example, you can create a generic Comparable interface that defines a method for comparing two objects of type T.
Classes: Generics can be used to create classes that can work with any type of data. For example, you can create a generic Repository class that provides CRUD operations for a data type T.
Modules
In TypeScript, a module is a way to organize code into reusable units. A module can contain classes, functions, interfaces, and other code constructs.
A module can be defined using the export keyword, which makes its contents available for use in other modules. You can also use the import keyword to import code from other modules.
Here’s an example of a module in TypeScript:
// math.tsexport function add(a: number, b: number): number { return a + b;}export function subtract(a: number, b: number): number { return a - b;}
In this example, the math.ts file defines a module that exports two functions (add and subtract). These functions can be imported and used in other modules:
In this example, the app.ts file imports the add and subtract functions from the math module using the import keyword.
Decorators
In TypeScript, a decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
Decorators can be used to modify the behavior of a class or its members, or to annotate them with additional metadata. For example, you can use a decorator to add logging or validation to a method, or to mark a property as required.
Here’s an example of a decorator in TypeScript:
function log(target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${key} with arguments: ${JSON.stringify(args)}`); const result = originalMethod.apply(this, args); console.log(`Result of ${key}: ${JSON.stringify(result)}`); return result; }; return descriptor;}class Calculator { @log add(a: number, b: number) { return a + b; }}const calculator = new Calculator();console.log(calculator.add(1, 2)); // logs "Calling add with arguments: [1,2]" and "Result of add: 3"
In this example, the log function is a decorator that takes three arguments: the target object (the class prototype), the name of the decorated method (add), and a descriptor object that describes the method. The log function modifies the behavior of the method by adding logging statements before and after the original method is called.
The Calculator class has a method add that is decorated with the @log decorator. When the add method is called, the log decorator is executed, which logs the arguments and result of the method.
Similarities can occur on the class level. For example, in this following example, you can write a logger for instantiation.
function log(target: any) { console.log(`Creating instance of ${target.name}`);}@logclass Calculator { add(a: number, b: number) { return a + b; }}const calculator = new Calculator(); // logs "Creating instance of Calculator"console.log(calculator.add(1, 2)); // logs 3
Key takeaways from short pieces advice Kevin Kelly has collected over decades. I've turned those into different categories of advice that has resonated with me.
Over the past month, I have been making small enhancements to make the website 1% better. I’m excited to talk about many of them, some noticeable, some under the hood.
Major Updates
Tailwind, 🌑 Dark Mode, and Solving My Woes with Styling
Some major updates are around styling. Previously, I was using some CSS files to style the different pages. However, after a lot of tinkering with Tailwind, I went full in. And boy has it made it easier to update styling, especially around media queries.
I hate to think about media queries, write a quick template or some variables, when tailwind makes that super easy. Add to your classes, sm: or md:, or lg: and you’re good to go for what you want to style at a specific breakpoint. What it means is, you have to think about your smallest width first, then move on to where you want your next breakpoint and add that utility class.
Where I immediately saw this benefit was a bug I found with a list of navigation links. With a little bit of tinkering, I was able to make the classes from flex with a default of no wrapping, to flex-wrap then md:flex-no-wrap to get that desired effect.
Another super helpful thing was immediately adding dark mode support. With the prefix, dark:, it made my life so much easier. I saw some initial places where I needed to modify the website immediately, and having this utility class prefix allowed me to get straight to styling vs. thinking about CSS selectors and rules. That said, having a background in CSS is a huge help, and even if I had to write only CSS, I’d still be operable.
Also, you may now notice there are links next to headings. That’s all thanks to installing my first rehype plugin! I had no idea there was such a rich community of rehype and remark plugins for all different use cases. I’m excited to explore more of them.
Lastly, my RSS feed has been updated. The books and films are added to the main feed, as well as styling the feed page itself. I didn’t know it used a .xsl file, and I used a generic template.
New Collections 📚 🎥
I expanded on my library. The films, anti-library, and book reviews have been added. The lindy library is passable and needs a bit more design and explanation. I’m proud of adding the log alongside the movies and books. I plan to link the log back to those pages too, where I can.
A lot of tinkering with Tailwind helped make this a lot easier too. I knew the design needed to look a little different, so for the past month, I’ve been browsing the pages on my own until they looked the way I wanted.
Content Updates
My resume, about page, now page, and many, many other pages have been updated. This was a long time coming. For years, I’ve kept the same about page, being about the “brief” version vs. the Twitter version vs. the LinkedIn version. I don’t think that represented me well. After some inspiration from Derek Sivers’ about page, I restarted the process from scratch. I thought more about it as an autobiography, rather than a professional LinkedIn page. It’s a lot more personal, and hopefully, that shines than the corporate version that I wince at.
Search 🔍
I installed the Algolia Crawler earlier in the month and finally had time to add a search component over the weekend. A little more refinement is needed to get the search results to display correctly (specifically removing the navigation text), but we’ll get there.
Under the hood 🚘
Getting the initial setup with Netlify was a major lift. Everything else has felt like a smaller lift, but also necessary. For example, dependabot wasn’t installed, so I made sure dealing with package updates was automated. I included meta tags that were missing for SEO purposes, verifying Twitter cards and Open Graph links (like LinkedIn) to work correctly.
What’s next?
Honestly, I’m going to take a step back and work on the content. While I’d love to update the Lindy Library, I’d much rather do what I do best, and write more blog posts. I have been working on a personal content pipeline to gather ideas, slowly work through books, and put my own take out there.
If there are any issues you find with the website, feel free to open an issue on GitHub. Ta ta for now 👋.
Changelog
#43 Add Github Actions to automatically tag the repository by semantic versioning
#45 Migrate from vanilla CSS to Tailwind CSS
#48 Dark Mode added!
#50 Additions to “Logs” - Add concert and musical for 2023
#51 Addition to “Library” - Add a brief lindy library
#54 Install Dependabot to Github project
#60, #61 Add first rehype plugin to add relative links to headings
#62 Netlify configuration - add security headers and update timezone
#66 Added top navigation for blog
#75 Expand on the films section and add them to the main page enhancement
#76 Extend press to include more talks, papers, and updated formatting enhancement
#82 Updated meta tags for SEO. Twitter cards and Open Graph links should correctly render updated hero images
#86 RSS page styled, should make it a lot more readable
#91 Migate blog posts with book reviews to the library book detailed pages
"Writing for Software Developers focuses on teaching you how to envision, create, and publish mid-length technical tutorials and articles, but the principles and practices you’ll develop will help you write anything from a short README to an entire technical book. And regardless of why you write, this guide aims to provide you with the techniques you will need to reach your goals."