Add a basic options page.
This commit is contained in:
parent
4d600e200d
commit
e0c4e8828e
|
@ -0,0 +1,14 @@
|
|||
import {Component} from "preact";
|
||||
import {PageHeader} from "./components/page-header.js";
|
||||
import {Tours} from "./components/tours.js";
|
||||
|
||||
export class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader />
|
||||
<Tours />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import {Component} from "preact";
|
||||
|
||||
export class PageHeader extends Component {
|
||||
render() {
|
||||
return (
|
||||
<header class="page-header">
|
||||
<img src="/tildes-shepherd.png" alt="The Tildes Shepherd logo." />
|
||||
<h1>Tildes Shepherd</h1>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import {Component, type JSX} from "preact";
|
||||
|
||||
type Props = {
|
||||
hasBeenCompleted: boolean;
|
||||
name: string;
|
||||
tourId: TourId;
|
||||
};
|
||||
|
||||
function tourDescription(tourId: Props["tourId"]): JSX.Element {
|
||||
if (tourId === "introduction") {
|
||||
return (
|
||||
<p class="tour-description">
|
||||
A short introduction to Tildes Shepherd and how the tours work. Normally
|
||||
this is automatically shown when you first installed the extension.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
if (tourId === "interface-homepage") {
|
||||
return (
|
||||
<p class="tour-description">
|
||||
Let's take a look at the home page and all we can do there.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<p class="tour-description">
|
||||
Tour ID "{tourId}" does not have a description, this should probably be
|
||||
fixed!
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function tourLink(tourId: Props["tourId"]): string {
|
||||
const anchor = `#tildes-shepherd-tour=${tourId}`;
|
||||
const baseUrl = "https://tildes.net";
|
||||
let path = "";
|
||||
|
||||
switch (tourId) {
|
||||
case "interface-homepage":
|
||||
case "introduction": {
|
||||
path = "/";
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
return `${baseUrl}${path}${anchor}`;
|
||||
}
|
||||
|
||||
export class Tour extends Component<Props> {
|
||||
render() {
|
||||
const {hasBeenCompleted, name, tourId} = this.props;
|
||||
const classes = ["tour", hasBeenCompleted ? "completed" : ""].join(" ");
|
||||
const completed = hasBeenCompleted ? (
|
||||
<p class="tour-completed" title="You've completed this tour before!">
|
||||
✔
|
||||
</p>
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<div class={classes.trim()}>
|
||||
<h3>{name}</h3>
|
||||
{completed}
|
||||
{tourDescription(tourId)}
|
||||
<p class="tour-link">
|
||||
<a href={tourLink(tourId)}>Take this tour</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import {Component, type JSX} from "preact";
|
||||
import {createToursCompleted} from "../../storage/common.js";
|
||||
import {Tour} from "./tour.js";
|
||||
|
||||
type Props = Record<string, unknown>;
|
||||
|
||||
type State = {
|
||||
toursCompleted: Awaited<ReturnType<typeof createToursCompleted>>["value"];
|
||||
};
|
||||
|
||||
export class Tours extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
toursCompleted: new Set(),
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount(): Promise<void> {
|
||||
const toursCompleted = await createToursCompleted();
|
||||
this.setState({toursCompleted: toursCompleted.value});
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const {toursCompleted} = this.state;
|
||||
|
||||
const createTour = (tourId: TourId, name: string): Tour["props"] => {
|
||||
return {
|
||||
hasBeenCompleted: toursCompleted.has(tourId),
|
||||
name,
|
||||
tourId,
|
||||
};
|
||||
};
|
||||
|
||||
const tourProps: Array<Tour["props"]> = [
|
||||
createTour("introduction", "Introduction"),
|
||||
createTour("interface-homepage", "The Homepage"),
|
||||
];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h2>Tours</h2>
|
||||
<div class="tours">
|
||||
{tourProps.map((props) => (
|
||||
<Tour {...props} />
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import {render} from "preact";
|
||||
import "modern-normalize/modern-normalize.css";
|
||||
import "../scss/global.scss";
|
||||
import {App} from "./app.js";
|
||||
|
||||
const preactRoot = document.createElement("div");
|
||||
document.body.append(preactRoot);
|
||||
render(<App />, preactRoot);
|
|
@ -0,0 +1,11 @@
|
|||
.page-header {
|
||||
display: flex;
|
||||
margin-top: 16px;
|
||||
padding: 0 16px;
|
||||
|
||||
img {
|
||||
aspect-ratio: 1;
|
||||
height: 5rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
@use "sass:list";
|
||||
|
||||
.tours {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.tour {
|
||||
background-color: var(--background-secondary);
|
||||
border: 2px solid var(--tour-accent-color);
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: min-content auto min-content;
|
||||
padding: 16px;
|
||||
|
||||
&.completed {
|
||||
grid-template-columns: auto min-content;
|
||||
|
||||
.tour-description,
|
||||
.tour-link {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
}
|
||||
|
||||
$border-colors: (
|
||||
"red",
|
||||
"orange",
|
||||
"yellow",
|
||||
"green",
|
||||
"cyan",
|
||||
"blue",
|
||||
"violet",
|
||||
"magenta",
|
||||
);
|
||||
|
||||
@each $color in $border-colors {
|
||||
$color-number: list.index($border-colors, $color);
|
||||
|
||||
&:nth-child(#{list.length($border-colors)}n + #{$color-number}) {
|
||||
--tour-accent-color: var(--#{$color});
|
||||
--tour-light-accent-color: var(--light-#{$color});
|
||||
}
|
||||
}
|
||||
|
||||
.tour-link a {
|
||||
color: var(--tour-light-accent-color);
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: var(--foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.tour-completed {
|
||||
color: var(--light-green);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
@use "reset";
|
||||
@use "components/page-header";
|
||||
@use "components/tours";
|
||||
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
body {
|
||||
--background-primary: #00171d;
|
||||
--background-secondary: #002b36;
|
||||
--background-tertiary: #000;
|
||||
--foreground: #fdf6e3;
|
||||
--red: #dc322f;
|
||||
--light-red: #e35d5b;
|
||||
--dark-red: #b9221f;
|
||||
--orange: #cb4b16;
|
||||
--light-orange: #e8632c;
|
||||
--dark-orange: #9d3a11;
|
||||
--yellow: #b58900;
|
||||
--light-yellow: #e8b000;
|
||||
--dark-yellow: #826200;
|
||||
--green: #859900;
|
||||
--light-green: #b1cc00;
|
||||
--dark-green: #596600;
|
||||
--cyan: #2aa198;
|
||||
--light-cyan: #35c9be;
|
||||
--dark-cyan: #1f7972;
|
||||
--blue: #268bd2;
|
||||
--light-blue: #4ca2df;
|
||||
--dark-blue: #1e6ea7;
|
||||
--violet: #6c71c4;
|
||||
--light-violet: #9094d3;
|
||||
--dark-violet: #484fb5;
|
||||
--magenta: #d33682;
|
||||
--light-magenta: #dc609c;
|
||||
--dark-magenta: #b02669;
|
||||
|
||||
background-color: var(--background-primary);
|
||||
color: var(--foreground);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-1);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--accent-2);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 16px;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
blockquote,
|
||||
code,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
li,
|
||||
ol,
|
||||
p,
|
||||
pre,
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
Loading…
Reference in New Issue