diff --git a/source/options/app.tsx b/source/options/app.tsx new file mode 100644 index 0000000..53eb4c8 --- /dev/null +++ b/source/options/app.tsx @@ -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 ( + <> + + + + ); + } +} diff --git a/source/options/components/page-header.tsx b/source/options/components/page-header.tsx new file mode 100644 index 0000000..bead87e --- /dev/null +++ b/source/options/components/page-header.tsx @@ -0,0 +1,12 @@ +import {Component} from "preact"; + +export class PageHeader extends Component { + render() { + return ( + + ); + } +} diff --git a/source/options/components/tour.tsx b/source/options/components/tour.tsx new file mode 100644 index 0000000..1813e8e --- /dev/null +++ b/source/options/components/tour.tsx @@ -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 ( +

+ A short introduction to Tildes Shepherd and how the tours work. Normally + this is automatically shown when you first installed the extension. +

+ ); + } + + if (tourId === "interface-homepage") { + return ( +

+ Let's take a look at the home page and all we can do there. +

+ ); + } + + return ( +

+ Tour ID "{tourId}" does not have a description, this should probably be + fixed! +

+ ); +} + +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 { + render() { + const {hasBeenCompleted, name, tourId} = this.props; + const classes = ["tour", hasBeenCompleted ? "completed" : ""].join(" "); + const completed = hasBeenCompleted ? ( +

+ ✔ +

+ ) : undefined; + + return ( +
+

{name}

+ {completed} + {tourDescription(tourId)} + +
+ ); + } +} diff --git a/source/options/components/tours.tsx b/source/options/components/tours.tsx new file mode 100644 index 0000000..35d7e18 --- /dev/null +++ b/source/options/components/tours.tsx @@ -0,0 +1,52 @@ +import {Component, type JSX} from "preact"; +import {createToursCompleted} from "../../storage/common.js"; +import {Tour} from "./tour.js"; + +type Props = Record; + +type State = { + toursCompleted: Awaited>["value"]; +}; + +export class Tours extends Component { + constructor(props: Props) { + super(props); + + this.state = { + toursCompleted: new Set(), + }; + } + + async componentDidMount(): Promise { + 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 = [ + createTour("introduction", "Introduction"), + createTour("interface-homepage", "The Homepage"), + ]; + + return ( +
+

Tours

+
+ {tourProps.map((props) => ( + + ))} +
+
+ ); + } +} diff --git a/source/options/setup.tsx b/source/options/setup.tsx new file mode 100644 index 0000000..a132e73 --- /dev/null +++ b/source/options/setup.tsx @@ -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(, preactRoot); diff --git a/source/scss/components/page-header.scss b/source/scss/components/page-header.scss new file mode 100644 index 0000000..7e15f84 --- /dev/null +++ b/source/scss/components/page-header.scss @@ -0,0 +1,11 @@ +.page-header { + display: flex; + margin-top: 16px; + padding: 0 16px; + + img { + aspect-ratio: 1; + height: 5rem; + margin-right: 1rem; + } +} diff --git a/source/scss/components/tours.scss b/source/scss/components/tours.scss new file mode 100644 index 0000000..16d0943 --- /dev/null +++ b/source/scss/components/tours.scss @@ -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); + } +} diff --git a/source/scss/global.scss b/source/scss/global.scss new file mode 100644 index 0000000..55b2ffc --- /dev/null +++ b/source/scss/global.scss @@ -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; +} diff --git a/source/scss/reset.scss b/source/scss/reset.scss new file mode 100644 index 0000000..83a98f6 --- /dev/null +++ b/source/scss/reset.scss @@ -0,0 +1,15 @@ +blockquote, +code, +h1, +h2, +h3, +h4, +h5, +li, +ol, +p, +pre, +ul { + margin: 0; + padding: 0; +}