Compare commits

..

No commits in common. "ad2ee507d8ac2d47b750efc40bba7129534031d7" and "9b59ff5e9d4347b204767c63694e7526eee1f1fe" have entirely different histories.

47 changed files with 21536 additions and 62230 deletions

13
.arc Normal file
View file

@ -0,0 +1,13 @@
@app
start-u1s
@static
@http
get /
@tables
data
scopeID *String
dataID **String
ttl TTL

View file

@ -18,7 +18,6 @@ Include the hardware you use, such as your computer and other related equipment.
* Ensure this PR has a title in the following format
* ✅ Add Your Name
* ✅ Add @twitterusername
* ✅ Add @mastodonusername@instance.url
* ❌ Add myself
* ❌ Adding myself!
* ❌ Add Your Name @twitter @github

View file

@ -11,13 +11,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 18.x
node-version: 13.x
- name: Cache/Restore node modules
uses: actions/cache@v4
uses: actions/cache@v1
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}

View file

@ -14,13 +14,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 18.x
node-version: 13.x
- name: Cache/Restore node modules
uses: actions/cache@v4
uses: actions/cache@v1
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}

6
.gitignore vendored
View file

@ -1,4 +1,3 @@
build/
# Logs
logs
*.log
@ -55,7 +54,9 @@ typings/
# dotenv environment variable files
.env*
# gatsby files
.cache/
public
# Mac files
.DS_Store
@ -77,6 +78,3 @@ haters/
.idea/
.history/
# Local Netlify folder
.netlify

View file

@ -1 +0,0 @@
18.19.0

4
.npmrc
View file

@ -1,4 +0,0 @@
fund=false
audit=false
legacy-peer-deps=true
shamefully-hoist=true

View file

@ -1,42 +0,0 @@
# Contributions to uses.dev
## Steps
1) Fork this repo
2) Add yourself to `/src/data.js`
3) When requesting a PR please read carefully.
4) Be nice to maintainers
## PR Guidelines
- Should be updated with the latest main or master branch.
- PR Title should be the name or handle of the person being added or update being made.
- A bad PR title `update data.js`
- A good PR Title `Adding Blake Campbell`
## What's a Uses Page?
A /uses page lists a developer's setup, gear, software, and configs (what they *use*). It's a great reference for those looking to add to their library of tools or reconfigure ones they already use.
**The URL MUST follow the format of use|uses|using|setup|environment at the end.**
### What Should I Include?
Include the hardware you use, such as your computer and other related equipment. Include your preferred terminal, text editors, tools, frameworks, and other related software you use. If you can, include configurations for software (such as fonts and themes). The more you have on your /uses page, the more interesting it'll be to those who view it - just keep it on-topic!
## Adding Yourself
* Ensure you are linking to a /uses page, **not just your website**
* Ensure your data is formatted like other entries
* Do not add yourself to the end of the array (add yourself somewhere random instead)
* Ensure this PR has a title in the following format
* ✅ Add Your Name
* ✅ Add @twitterusername
* ✅ Add @mastodonusername@instance.url
* ❌ Add myself
* ❌ Adding myself!
* ❌ Add Your Name @twitter @github
## Code Modifications
* Ensure the code submitted is formatted similarly to existing code
* Ensure variable, method, function, and component names are clear and concise

7
gatsby-browser.js Normal file
View file

@ -0,0 +1,7 @@
import React from 'react';
import { FilterProvider } from './src/context/FilterContext';
import './static/fonts.css';
export const wrapRootElement = ({ element }) => (
<FilterProvider>{element}</FilterProvider>
);

33
gatsby-config.js Normal file
View file

@ -0,0 +1,33 @@
module.exports = {
siteMetadata: {
title: `/uses`,
description: `A list of /uses pages detailing developer setups.`,
author: `@wesbos`,
siteUrl: 'https://uses.tech',
},
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
`gatsby-plugin-react-helmet`,
`gatsby-plugin-styled-components`,
],
};

91
gatsby-node.js Normal file
View file

@ -0,0 +1,91 @@
import people from './src/data.js';
import { tags, countries, devices, normalizeTag } from './src/util/stats';
function unique(arr) {
return Array.from(new Set(arr));
}
function sourceNodes({ actions, createNodeId, createContentDigest }) {
const normalizedTagMap = tags().reduce((acc, tag) => {
const normalizedTag = normalizeTag(tag.name);
acc[normalizedTag] = tag.name;
return acc;
}, {});
// Add People to the GraphQL API, we randomize the data on each build so no one gets their feelings hurt
people
.sort(() => Math.random() - 0.5)
.forEach(person => {
const normalizedPerson = {
...person,
// Clean out people that added basically the same tags twice
tags: unique(
person.tags.map(tag => normalizedTagMap[normalizeTag(tag)] || tag)
),
};
const nodeMeta = {
id: createNodeId(`person-${normalizedPerson.name}`),
parent: null,
children: [],
internal: {
type: `Person`,
mediaType: `text/html`,
content: JSON.stringify(normalizedPerson),
contentDigest: createContentDigest(normalizedPerson),
},
};
actions.createNode({ ...normalizedPerson, ...nodeMeta });
});
// Add tags to GraphQL API
tags().forEach(tag => {
const nodeMeta = {
id: createNodeId(`tag-${tag.name}`),
parent: null,
children: [],
internal: {
type: `Tag`,
mediaType: `text/html`,
content: JSON.stringify(tag),
contentDigest: createContentDigest(tag),
},
};
actions.createNode({ ...tag, ...nodeMeta });
});
// Add Countries to GraphQL API
countries().forEach(country => {
const nodeMeta = {
id: createNodeId(`country-${country.name}`),
parent: null,
children: [],
internal: {
type: `Country`,
mediaType: `text/html`,
content: JSON.stringify(country),
contentDigest: createContentDigest(country),
},
};
actions.createNode({ ...country, ...nodeMeta });
});
// Add Devices to GraphQL API
devices().forEach(device => {
const nodeMeta = {
id: createNodeId(`device-${device.name}`),
parent: null,
children: [],
internal: {
type: `device`,
mediaType: `text/html`,
content: JSON.stringify(device),
contentDigest: createContentDigest(device),
},
};
actions.createNode({ ...device, ...nodeMeta });
});
}
export { sourceNodes };

1
gatsby-ssr.js Normal file
View file

@ -0,0 +1 @@
export { wrapRootElement } from './gatsby-browser';

103
migration.md Normal file
View file

@ -0,0 +1,103 @@
We need to move these people over to the data.js file:
https://github.com/wesbos/awesome-uses/blob/master/src/data.js
Grab a random person, and fill out the info as best as possible.
If possible maybe ask the user on twitter to update or review theirs.
When done, check that person off.
* [x] [Wes Bos](https://wesbos.com/uses) — Web Developer, Tutorial Maker, Podcaster.
* [x] [Glenn Reyes](https://glennreyes.com/uses) - Independent Software Engineer, Trainer & Speaker.
* [x] [Smakosh](https://smakosh.com/the-tech-tools-I-use) - JavaScript Developer, indie maker.
* [ ] [Eric L. Barnes](https://ericlbarnes.com/uses/) - Laravel Developer, Maker, Writer
* [x] [Benjamin Lannon](https://lannonbr.com/uses/) - Web Developer, Open Source Contributor.
* [ ] [Thibault Maekelbergh](https://thibmaek.com/uses) - All-round developer, DIY enthousiast, record collector.
* [x] [Kent C. Dodds](https://kentcdodds.com/uses) - Web Developer, Educator, Live Streamer, Open Sourcerer.
* [ ] [Randy Oest, aka amazingrando](https://randyoest.com/uses/) - Lead Design and Frontend Engineer, Four Kitchens
* [ ] [Elijah Manor](https://elijahmanor.com/uses) - Front-End Developer and Educator
* [ ] [Dave Kiss](https://davekiss.com/uses) - Web Developer, Solopreneur, Adventurer
* [x] [Jonathan Suh](https://jonsuh.com/uses) - Designer, Developer
* [ ] [Manuel Wildauer](https://wildauer.io/uses) - Developer
* [ ] [Elliot Forbes](https://tutorialedge.net/uses/) - All-round Developer
* [ ] [Dr. Abstract](https://zimjs.com/uses/) - Founder of ZIM JavaScript Canvas Framework
* [ ] [Jay Collett](https://www.jaycollett.co/uses/) - Freelance web designer and front end developer with CraftCMS
* [ ] [Amit Merchant](https://www.amitmerchant.com/uses/) - Fullstack web developer, blogger.
* [ ] [Adam Greenough](https://adamgreenough.me/uses/) - Freelance Digital Designer & Web Developer
* [x] [Georgi Yanev](https://gyanev.com/uses/) - Web Developer, FPV drone pilot
* [ ] [Kumar Abhirup](https://kumar.now.sh/uses) - A 15yo Jnr. developer with a passion for learning 👋🏻
* [ ] [Chris Enns](https://chrisenns.com/uses/) - Podcast Editor & WordPress Wannabe
* [ ] [David Llop](https://davidllop.com/uses/) - Laravel & ChatBots Developer
* [ ] [Med Ben hartouz](https://benhartouz.com/uses/) - Fullstack Javascript Developer.
* [ ] [Łukasz Ostrowski](https://ostrowski.ninja/uses/) - Frontend developer
* [ ] [Tim Smith](https://www.iamtimsmith.com/uses) - Web developer, Blogger, and Freelancer
* [ ] [Jon Quach](https://jonquach.com/uses/) - Design Engineer
* [ ] [Tracy Osborn](https://limedaring.com/uses/) - Designer, Developer, Tech Author, Entreprenerd
* [ ] [Daniel Van Cuylenburg](https://dvanc.co/uses/) - Web Designer, Front-end Developer, Guitarist.
* [ ] [Aurel Tyson](https://aureltyson.info/uses) - iOS and backend developer
* [ ] [Nick Janetakis](https://nickjanetakis.com/uses) - Web developer, Sysadmin, Teacher
* [x] [Andrew Healey](https://healeycodes.com/uses/) - Fullstack Software Engineer, Blogger, Tutorial Creator.
* [ ] [Alex Carpenter](https://alexcarpenter.me/uses/) - Front-end Web Developer and Screencaster.
* [ ] [Wang Junxiao](http://www.feng0207.site/uses/) — Java Web Developer, Student.
* [x] [Jeff Wen](https://sinchang.me/uses/) - Web Developer, Open Source Contributor
* [ ] [Tracy Osborn](https://limedaring.com/uses/) - Designer, Developer, Tech Author, Entreprenerd
* [ ] [Bruno Brito](https://brunobrito.pt/uses/) - Web Developer, Content Creator, Digital Marketing 🇵🇹
* [ ] [Lemon 🍋](https://ahoylemon.xyz/uses/) - Web Developer, Podcaster, Human Who Makes Dumb Shit
* [ ] [Kevin Jalbert](https://kevinjalbert.com/uses/) - Developer Lead (React/Rails), Blogger.
* [x] [Swapnil Agarwal](https://swapnil.net/uses/) - Backend Developer, Aspiring Writer, Budding Designer
* [x] [Hugo Di Francesco](https://codewithhugo.com/uses/) - JavaScript Developer, Blogger
* [x] [Josiah Wiebe](https://jwie.be/uses/) - Full Stack Developer & Designer
* [ ] [Khalil Stemmler](https://khalilstemmler.com/uses/) - Fullstack Javascript Developer / Designer, Musician 🇨🇦
* [ ] [Pierre-Antoine _Leny_ Delnatte](https://leny.me/uses/) - Fullstack Web Developer, Bootcamp coach 🇧🇪
* [ ] [Harry Roberts](https://csswizardry.com/uses/) - Consultant Front-end Architect, designer, developer, writer and speaker.
* [ ] [Matt D. Smith](http://mds.is/using-stuff/) - Owner and Design Director at Studio Mds.
* [ ] [Ash Hitchcock](https://www.ashleyhitchcock.com/uses) - Front-end Developer 🇬🇧
* [ ] [Oscar te Giffel](https://oscartegiffel.com/uses/) - Fullstack Software engineer
* [ ] [John Michael Ferraris](https://jhnferraris.dev/uses/) - Fullstack Developer (that is still eager to learn), Runner
* [ ] [François Rabanel aka Pesko](https://peskoo.github.io/lasalledutemps/articles/2019-04/uses) - Fullstack Software Engineer
* [ ] [Jesse Burton](https://burtonmediainc.com/uses) - Web Developer, Freelancer, Blogger
* [ ] [Philipp John](https://www.jplace.de/uses) - Fullstack Web Developer
* [ ] [Enea Xharja](https://eneaxharja.com/uses) — Web Developer
* [ ] [Daniel Kim](https://www.danielkim.io/uses) — Software Engineer
* [ ] [Sam Baldwin](https://sambaldwin.info/uses) — Designer and front-end developer
* [ ] [Zack Eaton](https://zackeaton.com/uses/) - Student, Developer, Caffiene Enthusiast
* [x] [Brad Garropy](https://bradgarropy.com/uses) - self taught ⚛ frontender @ [adobe](https://www.adobe.com/). [blogger](https://bradgarropy.com), [streamer](https://youtube.com/bradgarropy), [tweeter](https://twitter.com/bradgarropy). 📝📺🐦
* [ ] [Stefan Zweifel](https://stefanzweifel.io/uses/) - Fullstack Web Developer
* [ ] [Ignacio Villanueva](https://ignaciodenuevo.com/uses) - Frontend Developer 🇪🇸
* [ ] [Sheree Peña](https://smariapena.com/uses) - Front Ender, Tester.
* [ ] [Pawel Grzybek](https://pawelgrzybek.com/uses/) - Software Engineer at Mindera
* [ ] [Jessica Dembe](https://www.jessicadembe.tech/uses/) - Software Engineer
* [ ] [Keziah Moselle](https://blog.keziahmoselle.fr/uses/) - Front-end developer
* [ ] [Scott Zirkel](https://scottzirkel.com/uses) - Developer, Designer, Artist, Writer
* [x] [Maxence Poutord](https://www.maxpou.fr/uses/) - Front-end Engineer and Nomadic worker
* [x] [Jonathan Speek](https://speek.design/uses/) - Fullstack Developer, Designer, Musician
* [ ] [Nervewax](https://nervewax.com/uses/) - Designer / Developer
* [ ] [Niko Heikkilä](https://nikoheikkila.fi/uses/) - Backend Developer & DevOps Engineer at Paytrail
* [ ] [Sil van Diepen](https://silvandiepen.nl/uses/) - Creative Front-end Developer
* [ ] [Matthias Hampel](https://dev.to/fullstack_to/tools-services-i-use-je9) - Fullstack Software Engineer / DevOps Enthusiast
* [ ] [Ste Grainer](https://stegrainer.com/uses) - Product designer, front-end developer, and writer
* [x] [Scott Tolinski](https://kit.com/leveluptutorials/podcasting-screencasting-gear) - Web Developer, Tutorial Maker, Podcaster.
* [ ] [Ben Hong](https://www.bencodezen.io/uses/) - Senior Frontend Engineer @ Meltano GitLab
* [ ] [Danny de Vries](https://dandevri.es/uses/) - Indie Maker and Lecturer
* [x] [Scott Spence](https://scottspence.me/uses) - Web Engineer @ Karmarama
* [ ] [Stephen Senkomago Musoke](https://ssmusoke.com/uses/) - Software Engineer [METS](https://mets.or.ug/), [UCSF Informatics Hub](https://globalhealthsciences.ucsf.edu/resources/informatics-hub) & PHP lover by night 🇺🇬
* [ ] [Gideon Bamuleseyo](https://medium.com/developer-circle-kampala/what-i-use-my-tools-of-trade-552655db4b8d) - Software Engineer [Andela](https://andela.com/), JavaScript junkie 🇺🇬
* [ ] [Jason Cory Alvernaz](https://jasoncoryalvernaz.com/uses/) - Fullstack Web Developer, Freelancer, Designer
* [ ] [Freek Van der Herten](https://freek.dev/uses/) - Developer, Package Creator, Conference Speaker, Blogger 🇧🇪
* [ ] [Adam Wathan](https://adamwathan.me/uses/) - Fullstack Web Developer, Entrepeneur, Maker of courses, Speaker, Blogger, Podcaster 🇨🇦
* [x] [Josh Manders](https://joshmanders.com/uses/) - Full Snack Developer and Indie Maker 🌯
* [x] [Daniel Wirtz](https://danielwirtz.com/uses/) - Designer who codes @Crisp Studio
* [x] [Harry Wolff](https://hswolff.com/uses/) - Front-end engineer and YouTouber
* [x] [Pouria Ezzati](https://pouria.dev/uses/) - Web developer
* [x] [James Mills](https://jamesmills.co.uk/uses/) - Web Consultant
* [x] [Jeffrey Way](https://laracasts.com/blog/laracasts-uses) - Laracasts author
* [x] [Terry Godier](https://terrygodier.com/uses/) - Developer and Marketer
* [x] [David O'Trakoun](https://www.davidosomething.com/uses/) - Software Engineer
* [x] [Nuno Maduro](https://nunomaduro.com/uses/) - Software engineer, Open Source contributor, Speaker
* [x] [Erno Salo](https://endormi.io/uses/) - Full Stack Developer and Open Source Contributor
* [x] [James Brooks](https://james.brooks.page/uses/) - Software Developer at Laravel and Podcaster
* [x] [Béla Varga](http://ecmanauten.de/uses/) - Front-end Developer, Meetup & Event Organizer and UX/UI Designer
[awesome-badge]: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg

View file

@ -1,13 +0,0 @@
[build]
command = "remix build"
publish = "public"
[dev]
command = "remix watch"
port = 3000
autoLaunch = false
[[headers]]
for = "/build/*"
[headers.values]
"Cache-Control" = "public, max-age=31536000, s-maxage=31536000"

52168
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,57 +5,64 @@
"author": "Wes Bos",
"eslintConfig": {
"extends": [
"wesbos/typescript"
"wesbos"
]
},
"engines": {
"node": ">= 18"
"node": ">= 12"
},
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@babel/core": "^7.21.0",
"@babel/preset-react": "^7.18.6",
"@netlify/edge-functions": "^2.0.0",
"@netlify/functions": "^1.4.0",
"@netlify/remix-edge-adapter": "^1.0.0",
"@remix-run/dev": "^1.13.0",
"@remix-run/netlify": "^1.13.0",
"@remix-run/node": "^1.13.0",
"@remix-run/react": "^1.13.0",
"@remix-run/serve": "^1.13.0",
"@remix-run/server-runtime": "^1.13.0",
"@types/node": "^18.14.0",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"country-emoji": "^1.5.6",
"isbot": "^3.6.6",
"joi": "^17.8.1",
"netlify-cli": "^13.0.0",
"@actions/core": "^1.2.1",
"@actions/exec": "^1.0.3",
"@actions/github": "^2.0.1",
"@hapi/joi": "^17.0.2",
"country-emoji": "^1.5.0",
"esm": "^3.2.25",
"gatsby": "^2.18.12",
"gatsby-image": "^2.2.34",
"gatsby-plugin-manifest": "^2.2.31",
"gatsby-plugin-offline": "^3.0.27",
"gatsby-plugin-react-helmet": "^3.1.16",
"gatsby-plugin-sharp": "^2.3.5",
"gatsby-plugin-styled-components": "^3.1.16",
"gatsby-plugin-web-font-loader": "^1.0.4",
"gatsby-source-filesystem": "^2.1.40",
"gatsby-transformer-sharp": "^2.3.7",
"normalize.css": "^8.0.1",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-is": "^18.2.0",
"styled-components": "5.3.6",
"typescript": "^4.9.5"
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-helmet": "^5.2.1",
"styled-components": "5.0.0-rc.3"
},
"scripts": {
"build": "netlify build",
"dev": "NODE_ENV=development netlify dev",
"readme": "node ./scripts/populate-readme.js"
"build": "npx --node-arg '-r esm' gatsby build",
"develop": "npx --node-arg '-r esm' gatsby develop",
"start": "npm run develop",
"serve": "npx --node-arg '-r esm' gatsby serve",
"clean": "gatsby clean"
},
"devDependencies": {
"@types/styled-components": "^5.1.26",
"eslint": "^8.34.0",
"eslint-config-wesbos": "^3.2.3",
"husky": "^8.0.3",
"lint-staged": "^13.1.2",
"postcss": "^8.4.21",
"postcss-nesting": "^11.2.1",
"prettier": "^2.8.4"
"@architect/sandbox": "^1.6.0",
"babel-eslint": "^9.0.0",
"eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.1",
"eslint-config-prettier": "^4.3.0",
"eslint-config-wesbos": "0.0.19",
"eslint-plugin-html": "^5.0.5",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-react-hooks": "^1.7.0",
"husky": "^4.0.10",
"lint-staged": "^9.5.0",
"prettier": "^1.19.1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/data.js": [

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
const postcssNesting = require("postcss-nesting");
module.exports = {
plugins: [postcssNesting()],
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 B

767
readme.md

File diff suppressed because it is too large Load diff

View file

@ -1,15 +0,0 @@
const { config } = require("@netlify/remix-edge-adapter");
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
...(process.env.NETLIFY || process.env.NETLIFY_LOCAL ? config : {}),
appDirectory: "src",
future: {
unstable_postcss: true,
},
ignoredRouteFiles: ["**/.*"],
server:
process.env.NETLIFY || process.env.NETLIFY_LOCAL
? "./server.js"
: undefined,
// serverBuildPath: ".netlify/functions-internal/server.js",
};

View file

@ -221,7 +221,6 @@ module.exports = [
'🇺🇬',
'🇺🇦',
'🇦🇪',
'🇺🇳',
'🇺🇾',
'🇺🇸',
'🇺🇿',
@ -238,4 +237,4 @@ module.exports = [
'🏳️‍🌈',
'🇪🇺',
'🏴󠁧󠁢󠁳󠁣󠁴󠁿',
];
];

View file

@ -1,13 +0,0 @@
import people from '../src/data.js';
function stringLength(str) {
return Array.from(new Intl.Segmenter().segment(str)).length;
}
function checkEmojiLength(person) {
if(stringLength(person.emoji) > 1 && person.emoji) {
console.log(person.name, person.emoji);
}
}
people.map(checkEmojiLength);

View file

@ -1,6 +1,5 @@
# → Visit [uses.tech](https://uses.tech) for a good time
## Please read [Contribution Guide](https://github.com/wesbos/awesome-uses/blob/master/contribution-guide.md) before submitting a PR.
A list of `/uses` pages detailing developer setups, gear, software and configs.
Add your own `/uses` page in [data.js](https://github.com/wesbos/awesome-uses/blob/master/src/data.js).

View file

@ -1,7 +1,7 @@
const exec = require('@actions/exec');
const core = require('@actions/core');
const github = require('@actions/github');
const Joi = require('joi');
const Joi = require('@hapi/joi');
const http = require('http');
const https = require('https');
const flags = require('./flags.js');
@ -13,8 +13,8 @@ async function getCurrentBranchName() {
const options = {
silent: true,
listeners: {
stdout: (data) => (myOutput += data.toString()),
stderr: (data) => (myError += data.toString()),
stdout: data => (myOutput += data.toString()),
stderr: data => (myError += data.toString()),
},
};
@ -23,7 +23,7 @@ async function getCurrentBranchName() {
}
/** on master branch will return an empty array */
module.exports.getMasterData = async function () {
module.exports.getMasterData = async function() {
const options = { silent: true };
const curentBranchName = await getCurrentBranchName();
// when on a branch/PR different from master
@ -61,15 +61,13 @@ module.exports.Schema = Joi.object({
.valid(...flags)
.required(),
twitter: Joi.string().pattern(new RegExp(/^@?(\w){1,15}$/)),
mastodon: Joi.string().pattern(new RegExp(/^@(\w){1,30}@(\w)+\.(.?\w)+$/)),
bluesky: Joi.string().pattern(new RegExp(/^[\w-]+\.(?:[\w-]+\.)?[\w-]+$/)),
emoji: Joi.string().allow(''),
computer: Joi.string().valid('apple', 'windows', 'linux', 'bsd'),
phone: Joi.string().valid('iphone', 'android', 'windowsphone', 'flipphone'),
computer: Joi.string().valid('apple', 'windows', 'linux'),
phone: Joi.string().valid('iphone', 'android', 'windowsphone','flipphone'),
tags: Joi.array().items(Joi.string()),
});
module.exports.getStatusCode = function (url) {
module.exports.getStatusCode = function(url) {
const client = url.startsWith('https') ? https : http;
return new Promise((resolve, reject) => {
const REQUEST_TIMEOUT = 10000;
@ -80,17 +78,17 @@ module.exports.getStatusCode = function (url) {
);
client
.get(url, (res) => {
.get(url, res => {
clearTimeout(timeoutId);
resolve(res.statusCode);
})
.on('error', (err) => reject(err));
.on('error', err => reject(err));
});
};
// If there are errors, will fail the action & add a comment detailing the issues
// If there are no errors, will leave an "all-clear" comment with relevant URLs (to ease a potential manual check)
module.exports.communicateValidationOutcome = async function (
module.exports.communicateValidationOutcome = async function(
errors,
failedUrls,
changedData
@ -101,8 +99,8 @@ module.exports.communicateValidationOutcome = async function (
comment += [
'🚨 We have detected the following issues, let us (contributors) know if you need support or clarifications:',
...errors.map((e) => `- ${e.message}`),
...failedUrls.map((url) => `- URL is invalid: ${url}`),
...errors.map(e => `- ${e.message}`),
...failedUrls.map(url => `- URL is invalid: ${url}`),
].join('\n');
} else {
comment += [
@ -120,14 +118,15 @@ module.exports.communicateValidationOutcome = async function (
'Cannot add a comment if GITHUB_TOKEN or context.payload.pull_request is not set'
);
core.info(`Comment contents:\n${comment}`);
return;
}
// TODO: Re-enable a way to comment on PRs that tests passed.
// const pullRequestNumber = context.payload.pull_request.number;
// const octokit = new github.getOctokit(GITHUB_TOKEN);
// await octokit.rest.pulls.createReviewComment({
// ...context.repo,
// pullRequestNumber,
// body: comment,
// });
const pullRequestNumber = context.payload.pull_request.number;
const octokit = new github.GitHub(GITHUB_TOKEN);
await octokit.issues.createComment({
...context.repo,
issue_number: pullRequestNumber,
body: comment,
});
};

View file

@ -1,21 +0,0 @@
// Import path interpreted by the Remix compiler
import * as build from '@remix-run/dev/server-build';
import { createRequestHandler } from '@netlify/remix-edge-adapter';
export default createRequestHandler({
build,
// process.env.NODE_ENV is provided by Remix at compile time
mode: process.env.NODE_ENV,
});
export const config = {
cache: 'manual',
path: '/*',
// Pass all assets to the netlify asset server
excluded_patterns: [
'^\\/_assets\\/[^\\/]*$',
'^\\/shared\\/[^\\/]*$',
'^\\/build\\/[^\\/]*$',
// '^\\/**\\/[^\\/]*$',
],
};

View file

@ -1,4 +1,28 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
const BackToTopLink = styled.a`
position: fixed;
bottom: 1%;
right: 1%;
background: var(--pink);
color: white;
background: rgba(0, 0, 0, 0.5);
cursor: pointer;
border-radius: 3px;
padding: 1rem;
transition: opacity 0.2s;
opacity: 0;
text-decoration: none;
${props =>
props.percent > 0.25 &&
`
opacity: 1;
`}
@media screen and (max-width: 500px) {
display: none;
}
`;
function useScrollPosition() {
const [percent, setPercent] = useState(0);
@ -25,8 +49,8 @@ function useScrollPosition() {
export default function BackToTop() {
const percent = useScrollPosition();
return (
<a className={`BackToTopLink ${percent > 0.25 ? 'Show' : ''}`} href="#top" title="Back To Top">
<BackToTopLink href="#top" title="Back To Top" percent={percent}>
&uarr;
</a>
</BackToTopLink>
);
}

View file

@ -1,39 +1,49 @@
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { name } from 'country-emoji';
import { useParams } from '@remix-run/react';
import styled from 'styled-components';
import { Tag, Tags } from './Topics';
import * as icons from '../util/icons';
export default function Person({ person }) {
function useIntersectionObserver(ref) {
const [isIntersecting, setIntersecting] = useState(false);
useEffect(function() {
const observer = new IntersectionObserver(function([entry]) {
console.log('Run once for every time its on screen');
console.log(entry);
});
// Observe the element we want to observve
observer.observe(ref.current);
return () => {
observer.unobserve(ref.current);
};
});
}
export default function Person({ person, currentTag }) {
const url = new URL(person.url);
const twitter = person.twitter
? `https://unavatar.io/x/${person.twitter.replace('@', '')}`
: null;
const website = `https://unavatar.io/${url.host}`;
const unavatar = person.twitter
? `${twitter}?fallback=${website}&ttl=28d`
: website;
const [_, mastodonHandle, mastodonServer] = person.mastodon?.split('@') || [];
const { tag: currentTag } = useParams();
const twitter = `https://unavatar.now.sh/twitter/${person.twitter}`;
const website = `https://unavatar.now.sh/${url.host}`;
const unavatar = person.twitter ? `${twitter}?fallback=${website}` : website;
const img = `https://images.weserv.nl/?url=${unavatar}&w=100&l=9&af&il&n=-1`;
return (
<div className="PersonWrapper">
<div className="PersonInner">
<PersonWrapper>
<PersonInner>
<header>
<img
width="50"
height="50"
src={unavatar}
src={img}
alt={person.name}
onError={({ currentTarget }) => {
currentTarget.onerror = null; // prevents looping
currentTarget.src = "/default.png";
}}
loading="lazy"
/>
<h3>
<a href={person.url} target="_blank" rel="noopener noreferrer">
{person.name}
</a>{" "}
</a>{' '}
{person.emoji}
</h3>
<a
@ -43,22 +53,19 @@ export default function Person({ person }) {
href={person.url}
>
{url.host}
{url.pathname.replace(/\/$/, "")}
{url.pathname.replace(/\/$/, '')}
</a>
</header>
<p>{person.description}</p>
<ul className="Tags">
{person.tags.map((tag) => (
<li
className={`Tag small ${tag === currentTag ? "currentTag" : ""}`}
key={tag}
>
<Tags>
{person.tags.map(tag => (
<Tag key={tag} as="li" currentTag={tag === currentTag} small>
{tag}
</li>
</Tag>
))}
</ul>
</div>
<div className="PersonDeets">
</Tags>
</PersonInner>
<PersonDeets>
<span className="country" title={name(person.country)}>
{person.country}
</span>
@ -78,64 +85,24 @@ export default function Person({ person }) {
)}
{person.twitter && (
<div className="SocialHandle">
<TwitterHandle>
<a
href={`https://twitter.com/${person.twitter.replace("@", "")}`}
href={`https://twitter.com/${person.twitter.replace('@', '')}`}
target="_blank"
rel="noopener noreferrer"
>
<span className="at">@</span>
{person.twitter.replace("@", "")}
{person.twitter.replace('@', '')}
</a>
</div>
</TwitterHandle>
)}
{/* If they have a bluesky, and no twitter/mastodon, show that */}
{person.bluesky && !person.twitter && (
<div className="SocialHandle">
<a
href={`https://bsky.app/profile/${person.bluesky.replace("@", "")}`}
target="_blank"
rel="noopener noreferrer"
>
<span className="at">@</span>
{person.bluesky.substring(1)}
</a>
</div>
)}
{/* If they have a mastodon, and no twitter, show that */}
{person.mastodon && !person.twitter && !person.bluesky && (
<div className="SocialHandle">
<a
href={`https://${mastodonServer}/@${mastodonHandle}`}
target="_blank"
rel="noopener noreferrer"
>
<span className="at">@</span>
{mastodonHandle}
</a>
</div>
)}
{/* If they have a bluesky, and no mastodon and no twitter, show that */}
{person.bluesky && !person.mastodon && !person.twitter && (
<div className="SocialHandle">
<a href={`https://bsky.app/profile/${person.bluesky}`}
target="_blank"
rel="noopener noreferrer"
>
<span className="at">@</span>
{person.bluesky}
</a>
</div>
)}
</div>
</div>
</PersonDeets>
</PersonWrapper>
);
}
Person.propTypes = {
currentTag: PropTypes.string,
person: PropTypes.shape({
github: PropTypes.string,
name: PropTypes.string,
@ -144,31 +111,109 @@ Person.propTypes = {
description: PropTypes.string,
tags: PropTypes.arrayOf(PropTypes.string),
country: PropTypes.string,
computer: PropTypes.oneOf(['apple', 'windows', 'linux', 'bsd']),
phone: PropTypes.oneOf(['iphone', 'android', 'windowsphone', 'flipphone']),
computer: PropTypes.oneOf(['apple', 'windows', 'linux']),
phone: PropTypes.oneOf(['iphone', 'android', 'windowsphone']),
twitter(props, propName, componentName) {
if (!/^@?(\w){1,15}$/.test(props[propName])) {
return new Error(
`Invalid prop \`${propName}\` supplied to` +
` \`${componentName}\`. This isn't a legit Twitter handle.`
);
}
},
mastodon(props, propName, componentName) {
if (!/^@(\w){1,30}@(\w)+\.(\w)+$/.test(props[propName])) {
return new Error(
`Invalid prop \`${propName}\` supplied to` +
` \`${componentName}\`. This isn't a legit Mastodon handle.`
);
}
},
bluesky(props, propName, componentName) {
if (!/^(\w)+\.(\w)+\.(\w)+$/.test(props[propName])) {
return new Error(
`Invalid prop \`${propName}\` supplied to` +
` \`${componentName}\`. This isn't a legit Bluesky handle.`
` \`${componentName}\`. This isn't a legit twitter handle.`
);
}
},
}),
};
// Component Styles
const PersonWrapper = styled.div`
border: 1px solid var(--vape);
border-radius: 5.34334px;
box-shadow: 10px -10px 0 var(--blue2);
display: grid;
grid-template-rows: 1fr auto auto;
`;
const PersonInner = styled.div`
padding: 2rem;
h3 {
margin: 0;
a:visited {
color: var(--purple);
}
}
header {
display: grid;
grid-template-rows: auto auto;
grid-template-columns: auto 1fr;
grid-gap: 0 1rem;
@media all and (max-width: 400px) {
grid-template-columns: 1fr;
}
img {
grid-row: 1 / -1;
background: var(--lightblue);
font-size: 1rem;
}
.displayLink {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-decoration: none;
color: var(--vape);
letter-spacing: 1px;
font-size: 1.2rem;
text-overflow: ellipsis;
max-width: 100%;
overflow: hidden;
:hover,
:visited {
color: var(--pink);
}
}
}
`;
const PersonDeets = styled.div`
display: flex;
border-top: 1px solid var(--vape);
> * {
flex: 1;
border-left: 1px solid var(--vape);
text-align: center;
padding: 1rem;
display: grid;
align-items: center;
justify-content: center;
grid-template-columns: auto auto;
&:first-child {
border-left: 0;
}
}
a {
color: var(--vape);
}
.country {
font-size: 3rem;
padding-top: 2rem;
}
.phone {
padding: 0;
}
@media all and (max-width: 400px) {
display: grid;
grid-template-columns: 1fr 1fr;
> *:nth-child(1),
> *:nth-child(2) {
/* lol */
border-bottom: 1px solid var(--vape);
}
}
`;
const TwitterHandle = styled.span`
font-size: 1.24323423426928098420394802rem;
.at {
color: var(--yellow);
margin-right: 2px;
}
`;

View file

@ -1,52 +1,123 @@
import { Link, useParams, useRouteLoaderData } from '@remix-run/react';
import React, { useContext } from 'react';
import styled from 'styled-components';
import FilterContext from '../context/FilterContext';
import * as icons from '../util/icons';
export default function Topics() {
const { tags, countries, devices } = useRouteLoaderData("root");
const params = useParams();
const currentTag = params.tag || 'all';
const { countries, tags, devices, currentTag, setCurrentTag } = useContext(
FilterContext
);
return (
<div className="Tags">
{tags.map((tag) => (
<Link
prefetch="intent"
key={`tag-${tag.name}`}
to={
tag.name === "all" ? "/" : `/like/${encodeURIComponent(tag.name)}`
}
className={`Tag ${currentTag === tag.name ? "currentTag" : ""}`}
<Tags>
{tags.map(tag => (
<Tag
currentTag={tag.name === currentTag}
htmlFor={`filter-${tag.name}`}
key={`filter-${tag.name}`}
clickable
>
<input
type="radio"
name="tag"
id={`filter-${tag.name}`}
value={tag.name}
checked={tag.name === currentTag}
onChange={e => setCurrentTag(e.currentTarget.value)}
/>
{tag.name}
<span className="TagCount">{tag.count}</span>
</Link>
<TagCount>{tag.count}</TagCount>
</Tag>
))}
{countries.map((tag) => (
<Link
to={`/like/${tag.emoji}`}
prefetch="intent"
className={`Tag ${currentTag === tag.emoji ? "currentTag" : ""}`}
{countries.map(tag => (
<Tag
currentTag={tag.emoji === currentTag}
htmlFor={`filter-${tag.name}`}
key={`filter-${tag.name}`}
title={tag.name}
clickable
>
<span className="TagEmoji">{tag.emoji}</span>
<span className="TagCount">{tag.count}</span>
</Link>
<input
type="radio"
name="tag"
id={`filter-${tag.name}`}
value={tag.emoji}
checked={tag.emoji === currentTag}
onChange={e => setCurrentTag(e.currentTarget.value)}
/>
<TagEmoji>{tag.emoji}</TagEmoji>
<TagCount>{tag.count}</TagCount>
</Tag>
))}
{devices.map((tag) => (
<Link
to={`/like/${tag.name}`}
className={`Tag ${currentTag === tag.name ? "currentTag" : ""}`}
prefetch="intent"
{devices.map(tag => (
<Tag
currentTag={tag.name === currentTag}
htmlFor={`filter-${tag.name}`}
key={`filter-${tag.name}`}
title={tag.name}
clickable
>
<input
type="radio"
name="computer"
id={`filter-${tag.name}`}
value={tag.name}
checked={tag.name === currentTag}
onChange={e => setCurrentTag(e.currentTarget.value)}
/>
<img height="20px" src={icons[tag.name]} alt={tag.name} />
<span className="TagCount">{tag.count}</span>
</Link>
<TagCount>{tag.count}</TagCount>
</Tag>
))}
</div>
</Tags>
);
}
// Component Styles
const Tags = styled.ul`
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
`;
const Tag = styled.label`
background: var(--pink);
margin: 2px;
border-radius: 3px;
font-size: ${props => (props.small ? `1.2rem;` : `1.7rem;`)};
padding: 5px;
color: hsla(0, 100%, 100%, 0.8);
transition: background-color 0.2s;
cursor: ${props => (props.clickable ? 'pointer' : 'default')};
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
input {
display: none;
}
${props =>
props.currentTag &&
`
background: var(--yellow);
color: hsla(0, 100%, 0%, 0.8);
`}
`;
const TagEmoji = styled.span`
transform: scale(1.45);
`;
const TagCount = styled.span`
background: var(--blue);
font-size: 1rem;
color: white;
padding: 2px;
border-radius: 2px;
margin-left: 5px;
`;
export { Tag, Tags };

View file

@ -1,23 +1,35 @@
import React from 'react';
import { Link } from 'gatsby';
import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import styled from 'styled-components';
import FavIcon from './FavIcon';
function Header({ siteTitle, siteDescription, siteUrl }) {
return (
<div className="header HeaderWrapper">
<HeaderWrapper className="header">
<FavIcon />
<Helmet>
<html lang="en" amp />
<title>{siteTitle}</title>
<meta name="description" content={siteDescription} />
<link rel="canonical" href={siteUrl} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content="@wesbos" />
<meta name="twitter:title" content={siteTitle} />
<meta name="twitter:description" content={siteDescription} />
<meta name="twitter:image" content={`${siteUrl}/twitter-card.png`} />
</Helmet>
<div>
<h1 id="top">
<a href="/">/uses</a>
<Link to="/">/uses</Link>
</h1>
<p>
A list of <code>/uses</code> pages detailing developer setups, gear,
software and configs.
</p>
</div>
</div>
</HeaderWrapper>
);
}
Header.propTypes = {
@ -33,3 +45,11 @@ Header.defaultProps = {
};
export default Header;
// Component Styles
const HeaderWrapper = styled.header`
text-align: center;
h1 {
font-size: 6rem;
}
`;

32
src/components/image.js Normal file
View file

@ -0,0 +1,32 @@
import React from 'react';
import { useStaticQuery, graphql } from 'gatsby';
import Img from 'gatsby-image';
/*
* This component is built using `gatsby-image` to automatically serve optimized
* images with lazy loading and reduced file sizes. The image is loaded using a
* `useStaticQuery`, which allows us to load the image from directly within this
* component, rather than having to pass the image data down from pages.
*
* For more information, see the docs:
* - `gatsby-image`: https://gatsby.dev/gatsby-image
* - `useStaticQuery`: https://www.gatsbyjs.org/docs/use-static-query/
*/
const Image = () => {
const data = useStaticQuery(graphql`
query {
placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
childImageSharp {
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
}
`);
return <Img fluid={data.placeholderImage.childImageSharp.fluid} />;
};
export default Image;

View file

@ -1,41 +1,134 @@
/**
* Layout component that queries for data
* with Gatsby's useStaticQuery component
*
* See: https://www.gatsbyjs.org/docs/use-static-query/
*/
import React from 'react';
import PropTypes from 'prop-types';
import { useStaticQuery, graphql } from 'gatsby';
import styled, { createGlobalStyle } from 'styled-components';
import Header from './header';
import 'normalize.css';
export default function Layout({ children }) {
const Layout = ({ children }) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
description
siteUrl
}
}
}
`);
return (
<main className="Main">
<Header />
{children}
<footer>
<center ya-i-used-a-center-tag="sue me">
<p>
Made by <a href="https://wesbos.com">Wes Bos</a> with{" "}
<a href="https://www.remix.run">Remix</a> ©{" "}
{new Date().getFullYear()}
</p>
<p>
Source on{" "}
<a href="https://github.com/wesbos/awesome-uses/">GitHub</a>. Add
yourself!
</p>
<p>
Icons from <a href="https://icons8.com">icons8.com</a>
</p>
<p>
Domain provided by <a href="https://get.tech/">.Tech</a>
</p>
<p>
Hosted on <a href="https://netlify.com">Netlify</a>
</p>
<p suppressHydrationWarning>Rendered Fresh</p>
</center>
</footer>
</main>
<>
<GlobalStyle />
<Main>
<Header
siteTitle={data.site.siteMetadata.title}
siteDescription={data.site.siteMetadata.description}
siteUrl={data.site.siteMetadata.siteUrl}
/>
{children}
<footer>
<center ya-i-used-a-center-tag="sue me">
<p>
Made by <a href="https://wesbos.com">Wes Bos</a> with{' '}
<a href="https://www.gatsbyjs.org">Gatsby</a> ©{' '}
{new Date().getFullYear() - Math.floor(Math.random() * 777)}
</p>
<p>
Source on{' '}
<a href="https://github.com/wesbos/awesome-uses/">GitHub</a>. Add
yourself!
</p>
<p>
Icons from <a href="https://icons8.com">icons8.com</a>
</p>
<p>
Domain provided by <a href="https://get.tech/">.Tech</a>
</p>
<p>
Hosted on <a href="https://netlify.com">Netlify</a>
</p>
</center>
</footer>
</Main>
</>
);
};
Layout.propTypes = {
children: PropTypes.node.isRequired,
};
export default Layout;
// Global Styles
const GlobalStyle = createGlobalStyle`
html {
--purple: #b066ff;
--blue: #203447;
--lightblue: #1f4662;
--blue2: #1C2F40;
--yellow: #ffc600;
--pink: #EB4471;
--vape: #d7d7d7;
background: var(--blue);
color: var(--vape);
font-family: 'Fira Mono', monospace;
font-weight: 100;
font-size: 10px;
scroll-behavior: smooth;
}
body {
font-size: 2rem;
overflow-y: scroll;
}
h1,h2,h3,h4,h5,h6 {
font-weight: 500;
}
a {
color: var(--yellow);
text-decoration-color: var(--pink);
font-style: italic;
}
code {
background: var(--lightblue);
}
::selection {
background: var(--yellow);
color: var(--blue);
}
body::-webkit-scrollbar {
width: 12px;
}
html {
scrollbar-width: thin;
scrollbar-color: var(--yellow) var(--blue);
}
body::-webkit-scrollbar-track {
background: var(--blue);
}
body::-webkit-scrollbar-thumb {
background-color: var(--yellow) ;
border-radius: 6px;
border: 3px solid var(--blue);
}
`;
// Component Styles
const Main = styled.main`
display: grid;
grid-gap: 3rem;
max-width: 1900px;
padding: 0 3rem;
margin: 5rem auto;
`;

View file

@ -0,0 +1,53 @@
import React, { createContext, useState } from 'react';
import { useStaticQuery, graphql } from 'gatsby';
import PropTypes from 'prop-types';
const FilterContext = createContext();
const FilterProvider = function({ children }) {
const [currentTag, setCurrentTag] = useState('all');
const { allTag, allCountry, allDevice } = useStaticQuery(graphql`
query FilterQuery {
allTag {
nodes {
name
count
}
}
allCountry {
nodes {
count
emoji
name
}
}
allDevice {
nodes {
count
name
}
}
}
`);
return (
<FilterContext.Provider
value={{
tags: allTag.nodes,
countries: allCountry.nodes,
devices: allDevice.nodes,
currentTag,
setCurrentTag,
}}
>
{children}
</FilterContext.Provider>
);
};
FilterProvider.propTypes = {
children: PropTypes.element,
};
export default FilterContext;
export { FilterProvider };

15347
src/data.js

File diff suppressed because it is too large Load diff

View file

@ -1,21 +0,0 @@
import { RemixBrowser } from '@remix-run/react';
import { startTransition, StrictMode } from 'react';
import { hydrateRoot } from 'react-dom/client';
const hydrate = () => {
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
);
});
};
if (window.requestIdleCallback) {
window.requestIdleCallback(hydrate);
} else {
// Safari doesn't support requestIdleCallback
// https://caniuse.com/requestidlecallback
window.setTimeout(hydrate, 1);
}

View file

@ -1,81 +0,0 @@
import type { EntryContext } from '@remix-run/node';
import { RemixServer } from '@remix-run/react';
import { renderToReadableStream } from 'react-dom/server';
const ABORT_DELAY = 5000;
export async function streamToText(stream: ReadableStream<Uint8Array>): Promise<string> {
let result = '';
const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
while (true) { // eslint-disable-line no-constant-condition
const { done, value } = await reader.read();
if (done) {
break;
}
result += value;
}
return result;
}
type CachedResponse = {
html: string;
date: Date;
}
const cache = new Map<string, CachedResponse>();
export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
// check if we have a cached response in memory
const cachedResponse = cache.get(request.url);
if (cachedResponse) {
// console.log('Serving from cache', request.url);
// if we have a cached response, check if it's less than 5 seconds old
const now = new Date();
const diff = now.getTime() - cachedResponse.date.getTime();
if (true || diff < 5000) {
// if it's less than 5 seconds old, return the cached response
responseHeaders.set('Content-Type', 'text/html');
return new Response(cachedResponse.html, {
headers: responseHeaders,
status: responseStatusCode,
});
}
}
let didError = false;
const chunks: Uint8Array[] = [];
const body = await renderToReadableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onError: (error: unknown) => {
didError = true;
console.error(error);
}
}
);
// tee the stream so we can cache it and send it to the client
const [toReponse, toCache] = body.tee();
streamToText(toCache).then(html => {
console.log('Caching', request.url);
cache.set(request.url, {
html: html.replace('Rendered Fresh', `Rendered from cache ${new Date().toISOString()}`),
date: new Date(),
});
});
const headers = new Headers(responseHeaders);
headers.set("Content-Type", "text/html");
const response = new Response(toReponse, {
headers,
status: didError ? 500 : responseStatusCode,
});
return response;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

62
src/pages/index.js Normal file
View file

@ -0,0 +1,62 @@
import React, { useContext } from 'react';
import { useStaticQuery, graphql } from 'gatsby';
import styled from 'styled-components';
import FilterContext from '../context/FilterContext';
import Layout from '../components/layout';
import Person from '../components/Person';
import Topics from '../components/Topics';
import BackToTop from '../components/BackToTop';
function IndexPage() {
const { currentTag } = useContext(FilterContext);
const { allPerson } = useStaticQuery(graphql`
query People {
allPerson {
nodes {
computer
country
description
emoji
id
name
phone
tags
twitter
url
}
}
}
`);
const people = allPerson.nodes.filter(
person =>
currentTag === 'all' ||
person.tags.includes(currentTag) ||
currentTag === person.country ||
currentTag === person.computer ||
currentTag === person.phone
);
return (
<Layout>
<Topics />
<People>
{people.map(person => (
<Person key={person.name} person={person} currentTag={currentTag} />
))}
</People>
<BackToTop />
</Layout>
);
}
export default IndexPage;
// Component Styles
const People = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
grid-gap: 5rem;
@media all and (max-width: 400px) {
grid-template-columns: 1fr;
}
`;

View file

@ -1,64 +0,0 @@
import type { LinksFunction, MetaFunction } from '@remix-run/node';
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts
} from '@remix-run/react';
import Layout from './components/layout';
import styles from './styles.css';
import { countries, devices, tags } from './util/stats';
import twitterCard from './images/twitter-card.png';
export const links: LinksFunction = () => [
{ rel: 'stylesheet', href: styles },
];
export function loader() {
return {
tags: tags(),
countries: countries(),
devices: devices(),
}
}
const metaData = {
description: `A list of /uses pages detailing developer setups.`,
siteUrl: 'https://uses.tech',
author: `@wesbos`,
title: '/uses',
}
export const meta: MetaFunction = () => ({
charset: 'utf-8',
title: '/uses',
viewport: 'width=device-width,initial-scale=1',
});
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<link rel="icon" href="https://fav.farm/🖥" />
<meta name="description" content={metaData.description} />
<link rel="canonical" href={metaData.siteUrl} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content="@wesbos" />
<meta name="twitter:title" content={metaData.title} />
<meta name="twitter:description" content={metaData.description} />
<meta name="twitter:image" content={`https://uses.tech${twitterCard}`} />
<Links />
</head>
<body>
<Layout>
<Outlet />
{/* <ScrollRestoration /> */}
<Scripts />
<LiveReload />
</Layout>
</body>
</html>
);
}

View file

@ -1,27 +0,0 @@
import { useLoaderData, useParams } from '@remix-run/react';
import { json, LoaderArgs } from '@remix-run/server-runtime';
import React, { useContext } from 'react';
import Topics from '../components/Topics';
import BackToTop from '../components/BackToTop';
import Person from '../components/Person';
import { getPeople } from 'src/util/stats';
export async function loader({ params }: LoaderArgs) {
const people = getPeople(params.tag);
return {people};
}
export default function Index() {
const { people } = useLoaderData<ReturnType<typeof loader>>();
return (
<>
<Topics />
<div className="People">
{people.map(person => (
<Person key={person.name} person={person} />
))}
</div>
<BackToTop />
</>
);
}

View file

@ -1 +0,0 @@
export { default, loader } from '../index';

View file

@ -1,291 +0,0 @@
/* Fonts */
@font-face {
font-family: 'Fira Mono';
font-weight: 400;
font-style: normal;
src: url('./fonts/fira_mono-regular-webfont.woff2') format('woff2'),
url('./fonts/fira_mono-regular-webfont.woff') format('woff');
font-display: swap;
}
@font-face {
font-family: 'Fira Mono';
font-weight: 400;
font-style: italic;
src: url('./fonts/fira_mono-regular_italic-webfont.woff2') format('woff2'), url('../src/fonts/fira_mono-regular_italic-webfont.woff') format('woff');
font-display: swap;
}
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
/* Global Styles */
:root {
--purple: #b066ff;
--blue: #203447;
--lightblue: #1f4662;
--blue2: #1C2F40;
--yellow: #ffc600;
--pink: #EB4471;
--vape: #d7d7d7;
background: var(--blue);
color: var(--vape);
font-family: 'Fira Mono', monospace;
font-weight: 100;
font-size: 10px;
scroll-behavior: smooth;
}
body {
font-size: 2rem;
overflow-y: scroll;
margin: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 500;
}
a {
color: var(--yellow);
text-decoration-color: var(--pink);
font-style: italic;
}
code {
background: var(--lightblue);
}
::selection {
background: var(--yellow);
color: var(--blue);
}
body::-webkit-scrollbar {
width: 12px;
}
html {
scrollbar-width: thin;
scrollbar-color: var(--yellow) var(--blue);
}
body::-webkit-scrollbar-track {
background: var(--blue);
}
body::-webkit-scrollbar-thumb {
background-color: var(--yellow);
border-radius: 6px;
border: 3px solid var(--blue);
}
.PersonWrapper {
border: 1px solid var(--vape);
border-radius: 5.34334px;
box-shadow: 10px -10px 0 var(--blue2);
display: grid;
grid-template-rows: 1fr auto auto;
}
.PersonInner {
padding: 2rem;
& h3 {
margin: 0;
& a:visited {
color: var(--purple);
}
}
& header {
display: grid;
grid-template-rows: auto auto;
grid-template-columns: auto 1fr;
grid-gap: 0 1rem;
@media all and (max-width: 400px) {
grid-template-columns: 1fr;
}
& img {
grid-row: 1 / -1;
font-size: 1rem;
}
& .displayLink {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-decoration: none;
color: var(--vape);
letter-spacing: 1px;
font-size: 1.2rem;
text-overflow: ellipsis;
max-width: 100%;
overflow: hidden;
&:hover,
&:visited {
color: var(--pink);
}
}
}
}
.PersonDeets {
display: flex;
border-top: 1px solid var(--vape);
>* {
flex: 1;
border-left: 1px solid var(--vape);
text-align: center;
padding: 1rem;
display: grid;
align-items: center;
justify-content: center;
grid-template-columns: auto auto;
&:first-child {
border-left: 0;
}
}
& a {
color: var(--vape);
}
& .country {
font-size: 3rem;
padding-top: 2rem;
}
& .phone {
padding: 0;
}
@media all and (max-width: 400px) {
display: grid;
grid-template-columns: 1fr 1fr;
>*:nth-child(1),
>*:nth-child(2) {
/* lol */
border-bottom: 1px solid var(--vape);
}
}
}
.SocialHandle {
font-size: 1.24323423426928098420394802rem;
& .at {
color: var(--yellow);
margin-right: 2px;
}
}
.Tags {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
}
.Tag {
background: var(--pink);
margin: 2px;
border-radius: 3px;
font-size: 1.7rem;
text-decoration: none;
&.small {
font-size: 1.2rem;
}
padding: 5px;
color: hsla(0, 100%, 100%, 0.8);
transition: background-color 0.2s;
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
& input {
display: none;
}
&.currentTag {
background: var(--yellow);
color: hsla(0, 100%, 0%, 0.8);
}
}
.TagEmoji {
transform: scale(1.45);
}
.TagCount {
background: var(--blue);
font-size: 1rem;
color: white;
padding: 2px;
border-radius: 2px;
margin-left: 5px;
}
.BackToTopLink {
position: fixed;
bottom: 1%;
right: 1%;
color: white;
background: rgba(0, 0, 0, 0.5);
cursor: pointer;
border-radius: 3px;
padding: 1rem;
transition: opacity 0.2s;
opacity: 0;
text-decoration: none;
&.Show {
opacity: 1;
}
@media screen and (max-width: 500px) {
display: none;
}
}
.HeaderWrapper {
text-align: center;
& h1 {
font-size: 6rem;
}
}
.Main {
display: grid;
grid-gap: 3rem;
max-width: 1900px;
padding: 0 3rem;
margin: 5rem auto;
}
.People {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
grid-gap: 5rem;
@media all and (max-width: 400px) {
grid-template-columns: 1fr;
}
}

View file

@ -5,6 +5,5 @@ import windows from '../images/windows.svg';
import apple from '../images/apple.svg';
import linux from '../images/linux.png';
import flipphone from '../images/flip-phone.png';
import bsd from '../images/bsd.png';
export { iphone, android, windowsphone, windows, apple, linux, flipphone, bsd };
export { iphone, android, windowsphone, windows, apple, linux, flipphone };

View file

@ -1,9 +1,8 @@
import { name } from 'country-emoji';
import people from '../data.js';
type Person = typeof people[0];
function merge(prop: string) {
return function (acc: any, obj: Record<any, any>) {
function merge(prop) {
return function(acc, obj) {
// Remove duplicated values.
const values = [...new Set(obj[prop])];
return [...values, ...acc];
@ -21,12 +20,12 @@ export function normalizeTag(tag) {
// Common mispellings currently seen in the data
// Do we want to go this far?
.replace(/frontend/i, 'Front End')
.replace(/TailwindCSS/i, 'Tailwind CSS')
.replace(/backend/i, 'Back End')
.replace(/fullstack/i, 'Full Stack')
.replace(/a11y/i, 'Accessibility')
.replace(/next.?js/i, 'Next')
.replace(/react.?js/i, 'React')
// Or is lowercase enough?
.toLowerCase()
);
@ -34,7 +33,7 @@ export function normalizeTag(tag) {
export function countries() {
const data = people
.map((person) => ({
.map(person => ({
name: name(person.country),
emoji: person.country,
}))
@ -53,8 +52,7 @@ export function countries() {
const sorted = Object.entries(data)
.map(([, country]) => country)
.sort((a, b) => b.count - a.count)
.filter(Boolean);
.sort((a, b) => b.count - a.count);
return sorted;
}
@ -64,6 +62,7 @@ export function tags() {
const counts = allTags.reduce(countInstances, {});
// sort and filter for any tags that only have 1
const tags = Object.entries(counts)
.sort(([, countA], [, countB]) => countB - countA)
// Only show the tag if this topic has 3 or more people in it
.filter(([, count]) => count >= 3)
.map(([name, count]) => ({ name, count }));
@ -84,60 +83,18 @@ export function tags() {
delete lowercaseTagMap[normalizedName];
}
return acc;
}, [])
// Sort by name first
.sort((a, b) => b.name.toLowerCase() > a.name.toLowerCase())
// Sort by count
.sort((a, b) => b.count - a.count);
}, []);
return [{ name: 'all', count: people.length }, ...normalizedTags];
}
export function devices() {
const all = [
...people.map((person) => person.computer),
...people.map((person) => person.phone),
].filter(Boolean);
...people.map(person => person.computer),
...people.map(person => person.phone),
];
return Object.entries(all.reduce(countInstances, {}))
.map(([device, count]) => ({ name: device, count }))
.sort((a, b) => b.count - a.count)
.map((device) => {
return device;
})
}
function unique(arr: string[]) {
return Array.from(new Set(arr));
}
const normalizedTagMap = tags().reduce((acc, tag) => {
const normalizedTag = normalizeTag(tag.name);
acc[normalizedTag] = tag.name;
return acc;
}, {});
export function getPeople(tag?: string) {
return [...people]
.sort(() => Math.random() - 0.5)
.map((person) => {
const normalizedPerson = {
...person,
// Clean out people that added basically the same tags twice
tags: unique(
person.tags.map((tag) => normalizedTagMap[normalizeTag(tag)] || tag)
),
};
return {
...normalizedPerson,
id: `person-${normalizedPerson.name}`,
};
})
.filter((person) => {
if (!tag) {
return true;
}
return person.tags.includes(tag) || person.country === tag || person.phone === tag || person.computer === tag;
})
.sort((a, b) => b.count - a.count);
}

16
static/fonts.css Normal file
View file

@ -0,0 +1,16 @@
/* Fonts */
@font-face {
font-family: 'Fira Mono';
font-weight: 400;
font-style: normal;
src: url('../src/fonts/fira_mono-regular-webfont.woff2') format('woff2'),
url('../src/fonts/fira_mono-regular-webfont.woff') format('woff');
font-display: swap;
}
@font-face {
font-family: 'Fira Mono';
font-weight: 400;
font-style: italic;
src: url('../src/fonts/fira_mono-regular_italic-webfont.woff2') format('woff2'), url('../src/fonts/fira_mono-regular_italic-webfont.woff') format('woff');
font-display: swap;
}

View file

Before

Width:  |  Height:  |  Size: 708 KiB

After

Width:  |  Height:  |  Size: 708 KiB

View file

@ -1,19 +0,0 @@
{
"include": ["remix.env.d.ts", "./**/*", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"strict": true,
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "node",
"esModuleInterop": true,
"module": "esnext",
"target": "esnext",
"baseUrl": ".",
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "react-jsx",
"noEmit": true,
"resolveJsonModule": true
}
}