我试图在GitHub Pages上整理编程竞赛的解说图片,形成故事书
首先
当我在解决AtCoder竞赛的问题时,偶尔会想要画解题图。我曾在2023年11月4日画了ABC327-F的解题图,并发布到X(Twitter)上。
有时候,在制作完图像之后,还会配上解说和代码示例,来完成一篇在Qiita上的文章。有时候,不愿意花费那么多时间,只满足于制作图像本身。这样一来,就不再会有人注意到这些图像了。
考虑到不想写内容,而只想简单整理图片的方式,我决定尝试使用流行的UI样本集Storybook作为目录。
- Storybook: Frontend workshop for UI development
这个感觉相当不错。可以在树形结构中切换图像,并且可以轻松地添加问题和答案的链接。
我不使用storybook作为UI目录或测试框架。虽然感觉我在为想要做的事情准备过于繁琐的工具,但我将把它尝试并写成一篇文章。
示威活动
-
- ツリーで、問題に対応するページを選べます
-
- 各ページを開くと、以下を見られます
解説画像: 1600 * 900px, 1枚以上
AtCoder 問題文へのリンク
AtCoder 解答例へのリンク、または Qiita 過去に投稿した解説記事へのリンク
解説画像をクリックすると拡大表示できます
本文所讨论的主题
-
- Storybook 7 + React 18 + TypeScript + Vite でプロジェクトを作成する
-
- Storybook でスライド画像を表示する
個々のスライドに対応する Docs (MDX) を作成する
スライドを共通で扱う UI コンポーネントを作成し、使用する
Storybook でその他調整
ツリー表示順
Open Graph protocol 設定
GitHub Pages に GitHub Actions でデプロイする
这里是源代码。
- hossy3/atcoder-slides: 競技プログラミングの解法を図示したスライド集
想要追求的受众群体
-
- React 18 + TypeScript が分かる方
- Storybook のプロジェクト作成・公開方法を知りたい方
创建项目
开发环境
提前准备。
Node.js 18
Visual Studio Code – Code Editing. Redefined
GitHub Desktop | Simple collaboration from your desktop
确认故事书的安装方法
故事书 一边盯着公式文档前进。许多库似乎可以通过npm安装(package)来获得。
- Install Storybook
在现有项目的根目录下,使用Storybook CLI以一个简单的命令进行安装。
Storybook 与其这样做,不如将其作为一个附加安装的方式集成到现有项目中。一般来说,这是因为我们首先开发了 UI 组件等内容。例如,当识别到一个 React 项目时,能提供适用于 React 的 Storybook 推荐配置并进行安装,这将是非常方便的。
因此,我们将创建一个临时项目来安装Storybook。在React中创建一个适当的UI组件。
前往此页面内的 “Create React App” 链接页。
确认React的安装方法
- React プロジェクトを始める – React
我进入了React官方网页。
最近,有很多关于在 Next.js 框架中安装 React 的方法被介绍出来。过去,我们只能选择 create-react-app。但如今时代已经变了。
好吧,那么问题是在哪个框架上安装 React……。这次我们想要做的是为安装 Storybook 框架做准备。说实话,我真的不需要框架,我想要的是最简洁的配置。
在安装方法下方有一个问题是“在没有框架的情况下可以使用 React 吗?”
使用npm获取react和react-dom,并使用类似Vite或Parcel的打包工具来设置自定义构建过程。
听说不太推荐,但是Vite可以做到。点击链接以继续。
快速安装 Vite + React。
-
- Vite | Next Generation Frontend Tooling
- Getting Started | Vite
我通过 Storybook 官方网站、React 官方网站,最后找到了 Vite 官方网站。官网上有写着安装命令。
npm create vite@latest my-app -- --template react-ts
这正是我想要的东西。
运行命令后,react和react-dom都安装了18.2版本。
作为模板,我们选择了我们熟悉的 React + TypeScript 构建方式。还有其他多个模板可供选择。
Vite 和 React 的操作确认
npm run dev
我已确认动作。
这个项目只是为了准备 Storybook。之后将不再使用。尽管如此,我们仍然感到进展顺利。
故事书安装
npx storybook@latest init
这次我们成功地安装了它。恭喜!
故事書的行動驗證
npm run storybook
显示了类似于Storybook中介绍的网站。
在故事书中展示幻灯片图像
项目结构和进行方式
故事书安装过程中创建了以下类型的文件。
.storybook/
main.ts
preview.ts
src/
stories/
Button.stories.ts
Button.tsx
Configure.mdx
Header.stories.ts
Header.tsx
Page.stories.ts
Page.tsx
assets/
*.png
接下来将逐渐更换为解释图像显示用途。
.storybook/
main.ts
preview.ts
src/
stories/
Readme.mdx
Slide.tsx
SlideAbc327d.mdx
SlideAbc327e.mdx
SlideAbc327f.mdx
assets/
abc327-d.png
abc327-e.png
abc327-f.png
制作每个页面的 MDX
- MDX
MDX 是对 Markdown 进行了扩展,使其能够嵌入 React 组件。
我们将使用MDX格式创建页面,可以在React组件中显示图像,并轻松添加附加信息,如链接等。
import { Meta } from "@storybook/blocks";
import { Slide } from "./Slide";
import Png from "./assets/abc327-d.png";
<Meta title="ABC327/ABC327-D" />
<Slide src={Png} layout="fullscreen" label="ABC327 D - Good Tuple Problem" />
- [D \- Good Tuple Problem](https://atcoder.jp/contests/abc327/tasks/abc327_d)
- [提出 \#47233850 \- HHKBプログラミングコンテスト2023\(AtCoder Beginner Contest 327\)](https://atcoder.jp/contests/abc327/submissions/47233850) (Rust)
元素与页面标题相对应。
<幻灯片>我们接下来将开始制作组件。
创建用于图像表示的组件
使用组件进行图片显示
显示下面的 。只能显示出来。
interface SlideProps {
src: string;
label?: string;
}
export const Slide = ({ src, label }: SlideProps) => {
return <img src={src} aria-label={label} />;
};
然而遗憾的是,Storybook 的 MDX 表格的最大宽度被限制在约1000像素。以1600 * 900的尺寸制作的图像将会缩小变形。我们希望能够进行100%宽度显示或等比例显示。
Horizontal 1000px seems to be determined by the style of Storybook itself. By specifying this style externally, the image should be displayed larger. However, it is possible that the method of specifying styles will change with the upgrade of Storybook. I don’t want to rely too much on it.
横向1000像素似乎是由Storybook本身的样式确定的。通过在外部指定此样式,图像应显示得更大。然而,随着Storybook升级,指定样式的方法可能会发生变化。我不想太过依赖它。
使用组件进行图像大小切换。
因此,我们在React组件中实现了可以切换大小的功能。
-
- 画像クリックで「初期サイズ (mode: 0)」「横幅100% (1)」「等倍 (2)」を切り替えられる
画像のスタイルを position: fixed にすれば、Storybook 右の
クリックできそうに見せるために、画像スタイルに cursor: zoom-in を付ける
[Enter] [Esc] でも拡大縮小できる
画像に tabIndex 属性を付けて、onKeyDown イベントを処理
Storybook のグローバルなキー処理に流れないように ev.stopPropagation(); も
这次写的 React 是唯一的一个。不是本文的主题,所以将其折叠起来。如果你有兴趣,请看一下。
Slide.tsx
import { KeyboardEventHandler, useCallback, useState } from “react”;
import “./Slide.css”;
interface SlideProps {
src: string;
label?: string;
}
export const Slide = ({ src, label }: SlideProps) => {
const [zoom, setZoom] = useState<0 | 1 | 2>(0);
const onKeyDown = useCallback
(ev) => {
if (ev?.key === “Enter”) {
setZoom(((zoom + 1) % 3) as 0 | 1 | 2);
ev.stopPropagation();
} else if (ev?.key === “Escape” && zoom > 0) {
setZoom(0);
ev.stopPropagation();
}
},
[zoom]
);
return (
<>
setZoom(1)}
onKeyDown={onKeyDown}
src={src}
aria-label={label}
tabIndex={0}
/>
{zoom > 0 && (
setZoom(0)} className=”modal-overlay”>
{
setZoom(2);
ev.stopPropagation();
}}
onKeyDown={onKeyDown}
tabIndex={0}
/>
) : (
)}
)}
>
);
};
Slide.tsx
Slide.tsx
import { KeyboardEventHandler, useCallback, useState } from “react”;
import “./Slide.css”;
interface SlideProps {
src: string;
label?: string;
}
export const Slide = ({ src, label }: SlideProps) => {
const [zoom, setZoom] = useState<0 | 1 | 2>(0);
const onKeyDown = useCallback
(ev) => {
if (ev?.key === “Enter”) {
setZoom(((zoom + 1) % 3) as 0 | 1 | 2);
ev.stopPropagation();
} else if (ev?.key === “Escape” && zoom > 0) {
setZoom(0);
ev.stopPropagation();
}
},
[zoom]
);
return (
<>
setZoom(1)}
onKeyDown={onKeyDown}
src={src}
aria-label={label}
tabIndex={0}
/>
{zoom > 0 && (
setZoom(0)} className=”modal-overlay”>
{
setZoom(2);
ev.stopPropagation();
}}
onKeyDown={onKeyDown}
tabIndex={0}
/>
) : (
)}
)}
>
);
}
轮播.tsx
.modal遮罩 {
定位: 固定;
顶部: 0;
左侧: 0;
宽度: 100%;
高度: 100%;
背景颜色: rgba(0, 0, 0, 0.5);
层级: 1;
光标: 放大;
}
.modal内容 {
定位: 绝对;
顶部: 0;
左侧: 0;
宽度: 100%;
高度: 100%;
溢出: 自动;
}
.modal内容 img.放大100 {
最大宽度: 取消;
光标: 放大;
}
img {
光标: 缩小;
}
在故事书中进行其他调整。
由于图片能够显示,所以我们已经达到了目标。
我们从这里开始,只需在能够处理的范围内稍作调整,让我们试着再调整一下。
显示源代码(不支持)
我认为,如果能够在图片旁边以折叠显示的方式展示源代码,能够更好地将图像的意象与实现结合在一起,对于观看者来说会更有帮助。
期待中的是在Markdown代码嵌入中,可以进行语言指定,例如 ““`rust”。但是,尝试过后发现格式没有被正确显示,而是和普通文本一样的显示。
故事书7似乎只支持一些语言,如JavaScript等。
-
Frequently Asked Questions
我为什么在Storybook MDX中的代码块没有高亮显示?
Storybook默认情况下提供对一组语言(如Javascript、Markdown、CSS、HTML、Typescript、GraphQL)的语法高亮支持,您可以在代码块中使用这些语言。目前,当您尝试注册自定义语言进行语法高亮时存在已知限制。我们正在努力修复这个问题,一旦修复可用,我们将在本节进行更新。
这次我们没有提供源代码显示,而是提供了指向AtCoder提交代码的链接。
- [提出 \#47233850 \- HHKBプログラミングコンテスト2023\(AtCoder Beginner Contest 327\)](https://atcoder.jp/contests/abc327/submissions/47233850) (Rust)
如果要進行相應的話,似乎有選擇使用Storybook 6系列,在Gist等地放置並嵌入代碼的方法。
重新排列树木
如果没有指定树的排序方式,页面名称将简单地按升序排序。旧的比赛会有更小的编号,并出现在较上方。
为了使最近的比赛页面更加显眼,我想要对其进行排序。
ABC308
ABC308-A
ABC308-B
ABC308-C
ABC308-D
ABC308-E
ABC308-Ex
ABC308-F
ABC308-G
ABC312
ABC312-E
ABC327
ABC327-D
ABC327-E
ABC327-F
ABC327
ABC327-D
ABC327-E
ABC327-F
ABC312
ABC312-E
ABC308
ABC308-A
ABC308-B
ABC308-C
ABC308-D
ABC308-E
ABC308-Ex
ABC308-F
ABC308-G
ABC327
ABC327-D
ABC327-E
ABC327-F
ABC312
ABC312-E
ABC308
ABC308-A
ABC308-B
ABC308-C
ABC308-D
ABC308-E
ABC308-F
ABC308-G
ABC308-Ex
按照比赛名称进行排序
命名组件和层次结构
在官方提供的資料中有對於重新排序的方法進行了說明。將preview.ts按照以下方式進行撰寫,只要規則較少就會變得簡單。
const preview: Preview = {
parameters: {
options: {
storySort: {
method: "alphabetical",
order: [
"はじめに",
"ABC327",
"ABC312",
"ABC308",
],
},
然而,每次添加比赛都需要更新 preview.ts 文件非常麻烦。我希望能更加简便一些。
写一个排序函数来进行排序。
在storySort中,你也可以指定一个函数。如果你想特殊处理以”abc”开头的故事名称,只需按照以下方式编写函数,就不需要每次进行维护工作了。
storySort: (a, b) => {
if (a.id === b.id) {
return 0;
}
if (!a.id.startsWith("abc") && !b.id.startsWith("abc")) {
return a.id.localeCompare(b.id);
}
if (!a.id.startsWith("abc") && b.id.startsWith("abc")) {
return -1;
}
if (a.id.startsWith("abc") && !b.id.startsWith("abc")) {
return 1;
}
if (a.id.substring(0, 6) === b.id.substring(0, 6)) {
if (!a.id.includes("-ex-") && b.id.includes("-ex-")) {
return -1;
}
if (a.id.includes("-ex-") && !b.id.includes("-ex-")) {
return 1;
}
return a.id.localeCompare(b.id);
}
return -a.id.localeCompare(b.id);
},
为了方便起见,我将 Ex 问题按照难易程度排序,并将其显示在最后一个位置。
更改标题(可能无法实现)
如果要发布网站,我想要指定标题
浏览器的窗口或选项卡中显示 “ABC327 / ABC327-D – Docs ⋅ Storybook”。根据这个显示,我们可以知道这是用 Storybook 制作的,但是不清楚这本 Storybook 是关于什么的。
但不幸的是,根据我的调查,我无法找到如何更改标题的方法。
-
Fluent UI React Components
作为一个参考,我看到了 Fluent UI v9 网页中的“Concepts / Introduction – Page ⋅ Storybook”,但标题中并没有包含“Fluent UI”的词语。也许这就是这个例子的特点。
Open Graph協議的設置
即使标题无法更改,也可以设置 Open Graph 协议(OGP)。这就足够了。
-
SNS などに URL を貼り付けると、タイトルと画像を表示するサービスもある 2
検索エンジンが、何に対する Storybook かということを理解できる
例如,当在Qiita上写入https://hossy3.github.io/atcoder-slides/时,会以这样的形式显示出来。
这个 OGP 的设置可以通过在
标签内嵌入用于 OGP 的标签来完成。Storybook的配置方法
-
Write a preset addon
也可以使用预设的方式编程修改预览的 head/body HTML,类似于预览-head.html/预览-body.html 可以用于配置故事渲染的方式。
(中略)
同样地,managerHead 可以用于修改周围的 “manager” UI,类似于manager-head.html。
建议使用 manager-head.html 来向整个页面的标题栏添加喜欢的信息。
-
fluentui/apps/public-docsite-v9/.storybook/manager-head.html at d82568581b73996acbfdf78966e55572e0f869f6 · microsoft/fluentui
我参考了Fluent UI v9的设置示例,写成了以下这样。
<meta name="title" content="競技プログラミング お絵描き広場" />
<meta
name="description"
content="競技プログラミングの解法を、感覚的にイメージでとらえられるようなものを描いています。数式は控えめです。"
/>
<meta property="og:type" content="website" />
<meta property="og:url" content="https://hossy3.github.io/atcoder-slides/" />
<meta property="og:title" content="競技プログラミング お絵描き広場" />
<meta
property="og:description"
content="競技プログラミングの解法を、感覚的にイメージでとらえられるようなものを描いています。数式は控えめです。"
/>
<meta property="og:locale" content="ja_JP" />
<meta
property="og:image"
content="https://hossy3.github.io/atcoder-slides/banner-meta.png"
/>
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="670" />
<meta property="og:image:type" content="image/png" />
<meta
property="og:image:alt"
content="競技プログラミング お絵描き広場 バナー"
/>
<meta property="twitter:card" content="summary_large_image" />
在故事书中,每一页显示不同的OGP似乎很困难。我本希望把每个幻灯片的图像直接放入og:image中。
到目前为止,我们对于调整部分就暂时告一段落。辛苦了。
使用GitHub动作在GitHub Pages上部署
我将发布我创建的网站。
建造
npm run build-storybook
故事书在安装过程中,还添加了构建命令。
当执行时,构建成果物将放置在storybook_static文件夹中。
.storybook/
src/
storybook_static/
assets/
abc*.png
Slide*.js
index.html
如果把storybook_static的内容放到某个网络服务器上,就完成了公开。
在 gh-pages 手动部署 (本次不进行)。
-
Paint.NET の選択範囲テキストを編集する Fluent UI React v9 アプリを作ってみた #React – Qiita
以前我发布服务的时候,我认为更新频率可能会很低,所以我通过gh-pages分支手动部署。应该可以以相同的方式进行发布。
这一次我们将使用 GitHub Actions 进行部署,以便更容易进行更新。
使用 GitHub Actions 进行部署
发布故事书
在公式页面中提到了推荐的设置方法。当主分支发生更改时,只需修改两个地方,即将storybook-static/以及其下的内容进行公开。
# Workflow name
name: Build and Publish Storybook to GitHub Pages
on:
# Event for the workflow to run on
push:
branches:
- "main"
permissions:
contents: read
pages: write
id-token: write
# List of jobs
jobs:
deploy:
runs-on: ubuntu-latest
# Job steps
steps:
# Manual Checkout
- uses: actions/checkout@v3
# Set up Node
- uses: actions/setup-node@v3
with:
node-version: "16.x"
#? Add Storybook build and deploy to GitHub Pages as a step in the workflow
- uses: bitovi/github-actions-storybook-to-github-pages@v1.0.1
with:
path: storybook-static
只需在 GitHub 仓库设置的 GitHub Pages 中,将源选择为 GitHub Actions (Beta),设置完成。以后的发布操作可以交给 GitHub Actions 处理。
最后
使用Storybook对图像进行整理,并在GitHub Pages上公开。辛苦了。
在系统稳定运行的同时,我会不定期地制作幻灯片。请多多关照。
在 Twitter 上以前会显示 og:title 等链接信息,但在2023年10月的规格更改中被删除。即使设置了信息,如何显示或不显示该信息仍然取决于各个服务提供商。