Rewritten in Astro

This project was no longer functional. This little cleanup was long overdue!
This commit is contained in:
Vojtěch Struhár 2023-12-29 16:15:41 +01:00
parent 5136cd8138
commit f456e802db
23 changed files with 4116 additions and 17171 deletions

7
astro.config.mjs Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import tailwind from "@astrojs/tailwind";
// https://astro.build/config
export default defineConfig({
integrations: [tailwind()]
});

16732
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,23 +4,16 @@
"private": true, "private": true,
"homepage": "https://VojtaStruhar.github.io/steam-review-template", "homepage": "https://VojtaStruhar.github.io/steam-review-template",
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.4", "@astrojs/tailwind": "^5.1.0",
"@react-hook/window-size": "^3.0.7", "astro": "^4.0.8",
"@testing-library/jest-dom": "^5.14.1", "tailwindcss": "^3.4.0"
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"react": "^17.0.2",
"react-collapsible": "^2.8.3",
"react-collapsible-component": "^1.3.4",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.1.2"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "dev": "astro dev",
"build": "react-scripts build", "start": "astro dev",
"test": "react-scripts test", "build": "astro build",
"eject": "react-scripts eject", "preview": "astro preview",
"astro": "astro",
"predeploy": "npm run build", "predeploy": "npm run build",
"deploy": "gh-pages -d build" "deploy": "gh-pages -d build"
}, },

3882
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/steam.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="Steam checklist review template"
content="Steam checklist review template categories check"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/steam_512.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Steam Review Template</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,45 +0,0 @@
.App-header {
background-color: #282c34;
min-height: 8vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
h2.App-header {
min-height: auto;
margin-top: 1vmin;
margin-bottom: 1vmin;
}
p.App-header {
min-height: auto;
margin-top: 4px;
margin-bottom: 4px;
font-size: calc(5px + 2vmin);
}
/* middle */
div.categories-container {
margin-bottom: 8px;
}
/* footer */
.App-footer {
background-color: #1e2127;
min-height: auto;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
font-size: calc(3px + 1vmin);
color: white;
}
p.signature {
margin: 16px;
font-size: small;
}

View File

@ -1,24 +0,0 @@
import "./App.css";
import Categories from "./Categories";
import config_json from "./review_templates/config_rdr2.json";
function App() {
return (
<div>
<header className="App-header">
<h2 className="App-header">{"---{ Review Template }---"}</h2>
<p className="App-header"> Awesome</p>
</header>
<div className="categories-container">
<Categories props={config_json.categories} />
</div>
<footer className="App-footer">
<p className="signature">Vojtěch Struhár, 2021</p>
</footer>
</div>
);
}
export default App;

View File

@ -1,37 +0,0 @@
p.inner {
background-color: tomato;
};
div.section {
text-align: left;
align-items: left;
border-style: solid;
border-radius: 8px;
border-width: 2px;
};
.centered {
height: 10vh; /* Magic here */
display: flex;
justify-content: center;
align-items: center;
}
.button-centered {
min-height: 10vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
flex-wrap: nowrap;
margin: 8px;
}
p.review {
width: 40%;
display: flex;
text-align: center;
justify-content: center;
flex-direction: column;
margin: 8px;
}

View File

@ -1,97 +0,0 @@
import './Categories.css';
import React, { useState } from 'react';
import CheckboxOption from './components/CheckboxOption';
import RadioOption from './components/RadioOption';
import { Button } from '@material-ui/core';
export default function Categories(props) {
const [reviewInfo, setReviewInfo] = useState("")
const create_categories = () => {
var array = []
for (let i = 0; i < props.props.length; i++) {
const element = props.props[i];
if (element.type === "radio") {
array.push(<RadioOption props={element} />)
} else if (element.type === "check") {
array.push(<CheckboxOption props={element} />)
}
}
return array
}
const categoryComponents = create_categories()
const generate_review = () => {
var localReviewString = ""
function appendCategoryTitle(title) {
localReviewString += "---{ " + title + " }---\n"
}
function appendOption(option, checked) {
localReviewString += (checked ? "☑ " : "☐ ") + option + "\n"
}
for (let i = 0; i < props.props.length; i++) {
const categoryJson = props.props[i];
appendCategoryTitle(categoryJson.title)
// With radio, only one option is selected
if (categoryJson.type === "radio") {
const saved = sessionStorage.getItem(categoryJson.title) || ""
categoryJson.options.forEach(option => {
appendOption(option, saved === option)
});
} else if (categoryJson.type === "check") {
// With checkbox, multiple options can be selected
const selectedOptions = JSON.parse(sessionStorage.getItem(categoryJson.title) || "[]")
categoryJson.options.forEach(option => {
appendOption(option, selectedOptions.includes(option))
});
} else {
localReviewString += "ERROR - bad category type. (radio | check)\n"
}
// newline under every category
localReviewString += "\n"
}
// Credit
localReviewString += "\nGrab this review template here! 👉 https://vojtastruhar.github.io/steam-review-template/\n"
navigator.clipboard.writeText(localReviewString).then(function () {
console.log('Async: Copying to clipboard was successful!');
setReviewInfo("The review has been copied into your clipboard!")
}, function (err) {
console.error('Async: Could not copy text: ', err);
setReviewInfo("Copying into clipboard failed. New window with the review should appear, please, copy it manually.")
check_review_in_new_window(localReviewString)
});
}
const check_review_in_new_window = (text) => {
var newWin = window.open('url', 'Steam review', 'height=700,width=500,scrollbars=yes,resizable=yes');
newWin.document.write(String.raw`${text.replaceAll("\n", "<br/>")}`);
}
return (
<div>
<div className="centered">
<div>
{categoryComponents}
</div>
</div>
<div className="button-centered">
<Button variant="contained" color="primary" onClick={generate_review}>
Generate Steam Review
</Button>
{reviewInfo !== "" &&
<p className="review" >{reviewInfo}</p>
}
</div>
</div>
)
}

View File

@ -1,43 +0,0 @@
import "./Option.css";
import React from "react";
import MyCheckbox from "./MyCheckbox";
import { useWindowWidth } from "@react-hook/window-size";
export default function CheckboxOption(props) {
props = props.props;
const windowWidth = useWindowWidth();
const create_checkboxes = () => {
var array = [];
for (let i = 0; i < props.options.length; i++) {
const element = props.options[i];
array.push(
<MyCheckbox
props={{ title: element, index: i, category: props.title }}
/>
);
}
return array;
};
const checkboxes = create_checkboxes();
props.get_selected = () => {
var checked = [];
checkboxes.forEach((element) => {
if (element.props.props.isOn) {
checked.push(element.props.props.title);
}
});
return checked;
};
return (
<div
className="radio-container"
style={{ width: windowWidth < 1000 ? "90%" : "40%" }}
>
<h3 className="snug">{props.title}</h3>
{checkboxes}
</div>
);
}

View File

@ -1,34 +0,0 @@
import "./Option.css";
import React, { useState } from "react";
import { Checkbox, FormControlLabel } from "@material-ui/core";
import arrayRemove from "../utility/arrays-helper";
export default function MyCheckbox(props) {
const [isChecked, setIsChecked] = useState(false);
const toggle = () => {
props.props.isOn = !isChecked;
var saved = JSON.parse(
sessionStorage.getItem(props.props.category) || "[]"
);
if (!isChecked) {
saved.push(props.props.title);
} else {
saved = arrayRemove(saved, props.props.title);
}
sessionStorage.setItem(props.props.category, JSON.stringify(saved));
setIsChecked(!isChecked);
};
return (
<FormControlLabel
value={props.props.index}
control={<Checkbox color="primary" onClick={toggle} />}
label={props.props.title}
labelPlacement="end"
checked={isChecked}
/>
);
}

View File

@ -1,22 +0,0 @@
div.radio-container {
margin: auto;
/* Width manually overriden depending on window width */
width: 40%;
display: flex;
align-items: flex-start;
justify-content: center;
flex-direction: column;
border: solid;
margin-top: 8px;
border-radius: 6px;
border-width: 2px;
border-color: slategray;
padding: 4px;
padding-left: 12px;
background-color: aliceblue;
}
.snug {
margin-top: 4px;
margin-bottom: 4px;
}

View File

@ -1,41 +0,0 @@
import "./Option.css";
import React from "react";
import { FormControlLabel, Radio, RadioGroup } from "@material-ui/core";
import { useWindowWidth } from "@react-hook/window-size";
export default function RadioOption(props) {
const windowWidth = useWindowWidth();
props = props.props;
props.selectedState = "";
const radioClicked = (event) => {
props.selectedState = event.target.value;
sessionStorage.setItem(props.title, event.target.value);
};
const choices = () => {
var array = [];
for (let i = 0; i < props.options.length; i++) {
const element = props.options[i];
array.push(
<FormControlLabel
value={element}
control={<Radio color="primary" />}
label={element}
/>
);
}
return array;
};
return (
<div
className="radio-container"
style={{ width: windowWidth < 1000 ? "90%" : "40%" }}
>
<h3 className="snug">{props.title}</h3>
<RadioGroup onChange={radioClicked}>{choices()}</RadioGroup>
</div>
);
}

1
src/env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="astro/client" />

View File

@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -1,17 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -0,0 +1,46 @@
---
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
<style is:global>
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.5;
min-height: 100vh;
background: #fff;
color: #000;
}
html {
font-family: system-ui, sans-serif;
}
code {
font-family:
Menlo,
Monaco,
Lucida Console,
Liberation Mono,
DejaVu Sans Mono,
Bitstream Vera Sans Mono,
Courier New,
monospace;
}
</style>

161
src/pages/index.astro Normal file
View File

@ -0,0 +1,161 @@
---
import config_json from "../review_templates/config_rdr2.json";
import PageLayout from "../layouts/PageLayout.astro";
---
<PageLayout title="Steam Review Template">
<header class="bg-slate-700 text-white py-2 flex flex-col items-center">
<h2>{"---{ Review Template }---"}</h2>
<p class="mt-0">☑ Awesome</p>
</header>
<div class="max-w-screen-sm mx-auto px-4">
<p>
This little tool simplifies the creation of copy-pasta Steam reviews (if
you are on this website, this <em>is</em> what you are looking for). I
find them moderately funny, so I made this website to avoid copy-pasting
the copy pasta so much.&nbsp;😆
</p>
<p>
The code is open source &rarr; <a
href="https://github.com/VojtaStruhar/steam-review-template"
>
check it out on Github</a
>!
</p>
{
config_json.categories.map((category) => (
<form class="flex flex-col gap-2 ">
<fieldset class="rounded-lg my-4 shadow border bg-sky-100">
<legend class="font-mono font-bold text-xl bg-emerald-300 px-4 rounded-lg shadow">
{category.title}
</legend>
{category.options.map((item) => (
<>
{category.type == "radio" ? (
<input
type="radio"
id={item}
name={category.title}
value={item}
/>
) : (
<input
type="checkbox"
id={item}
name={category.title}
value={item}
/>
)}
<label class="mx-2" for={item}>
{item}
</label>
<br />
</>
))}
</fieldset>
</form>
))
}
<div class="flex justify-center">
<button
id="output-button"
class="py-4 px-8 text-lg font-bold rounded-lg border-none shadow bg-amber-400 hover:cursor-pointer hover:shadow-lg transition-all"
>
Generate Steam Review!</button
>
</div>
<div class="flex justify-center mt-4">
<button
id="copy-output"
class="py-2 px-4 text-lg rounded-lg border-none shadow bg-sky-100 hover:cursor-pointer hover:shadow-lg transition-all"
>
Copy output</button
>
</div>
<pre id="output"></pre>
</div>
<script>
var data = {};
import config_json from "../review_templates/config_rdr2.json";
function showOutput() {
copyButton?.style.setProperty("display", "block");
let output = document.getElementById("output");
if (output) {
output.innerHTML = "";
config_json.categories.forEach((category) => {
output.innerHTML += `\n---{ ${category.title} }---\n`;
category.options.forEach((item) => {
if (
category.type === "check" &&
data[category.title].includes(item)
) {
output.innerHTML += `☑ ${item}\n`;
} else if (data[category.title] === item) {
output.innerHTML += `☑ ${item}\n`;
} else {
output.innerHTML += `☐ ${item}\n`;
}
});
});
}
}
let outputButton = document.getElementById("output-button");
if (outputButton) {
outputButton.addEventListener("click", showOutput);
}
let copyButton = document.getElementById("copy-output");
if (copyButton) {
copyButton.addEventListener("click", () => {
navigator.clipboard.writeText(
document.getElementById("output").innerText
);
});
}
copyButton?.style.setProperty("display", "none");
[...document.getElementsByTagName("input")].forEach((input) => {
if (input.type == "checkbox") {
data[input.name] = [];
}
input.addEventListener("change", (event) => {
let target = event.target;
if (target) {
if (target.type == "checkbox") {
if (target.checked) {
data[target.name].push(target.value);
} else {
data[target.name] = data[target.name].filter(
(item) => item != target.value
);
}
} else {
data[target.name] = target.value;
}
}
});
});
</script>
<footer class="bg-slate-700 text-white py-2 flex flex-col items-end">
<p class="px-8">
<a
href="https://www.vojtechstruhar.com"
target="_blank"
class="text-white">Vojtěch Struhár</a
>, 2021
</p>
</footer>
</PageLayout>
<style>
input[type="radio"],
input[type="checkbox"] {
transform: scale(1.25);
}
</style>

View File

@ -1,13 +0,0 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@ -1,5 +0,0 @@
export default function arrayRemove(arr, value) {
return arr.filter(function (ele) {
return ele !== value;
});
}

11
tailwind.config.mjs Normal file
View File

@ -0,0 +1,11 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
theme: {
extend: {},
},
plugins: [],
corePlugins: {
preflight: false,
},
};