|
1 | 1 | "use client"; |
2 | 2 |
|
3 | 3 | import { useEffect, useRef, useState } from "react"; |
4 | | -import { Terminal } from "@xterm/xterm"; |
5 | | -import { FitAddon } from "@xterm/addon-fit"; |
| 4 | +import type { Terminal } from "@xterm/xterm"; |
| 5 | +import type { FitAddon } from "@xterm/addon-fit"; |
6 | 6 | import "@xterm/xterm/css/xterm.css"; |
7 | 7 | import chalk from "chalk"; |
8 | 8 | import { useChangeTheme } from "../[docs_id]/themeToggle"; |
@@ -70,104 +70,110 @@ export function useTerminal(props: TerminalProps) { |
70 | 70 |
|
71 | 71 | // ターミナルの初期化処理 |
72 | 72 | useEffect(() => { |
73 | | - const abortController = new AbortController(); |
74 | | - // globals.cssでフォントを指定し読み込んでいるが、 |
75 | | - // それが読み込まれる前にterminalを初期化してしまうとバグる。 |
76 | | - document.fonts.load("0.875rem Inconsolata Variable").then(() => { |
77 | | - if (!abortController.signal.aborted) { |
78 | | - const fromCSS = (varName: string) => |
79 | | - window.getComputedStyle(document.body).getPropertyValue(varName); |
80 | | - // "--color-" + color_name のように文字列を分割するとTailwindCSSが認識せずCSSの値として出力されない場合があるので注意 |
81 | | - const term = new Terminal({ |
82 | | - cursorBlink: true, |
83 | | - convertEol: true, |
84 | | - cursorStyle: "bar", |
85 | | - cursorInactiveStyle: "none", |
86 | | - fontSize: 14, |
87 | | - lineHeight: 1.4, |
88 | | - letterSpacing: 0, |
89 | | - fontFamily: "'Inconsolata Variable','Noto Sans JP Variable'", |
90 | | - theme: { |
91 | | - // DaisyUIの変数を使用してテーマを設定している |
92 | | - // TODO: ダークテーマ/ライトテーマを切り替えたときに再設定する? |
93 | | - background: fromCSS("--color-base-300"), |
94 | | - foreground: fromCSS("--color-base-content"), |
95 | | - cursor: fromCSS("--color-base-content"), |
96 | | - selectionBackground: fromCSS("--color-primary"), |
97 | | - selectionForeground: fromCSS("--color-primary-content"), |
98 | | - black: fromCSS("--color-black"), |
99 | | - brightBlack: fromCSS("--color-neutral-500"), |
100 | | - red: fromCSS("--color-red-600"), |
101 | | - brightRed: fromCSS("--color-red-400"), |
102 | | - green: fromCSS("--color-green-600"), |
103 | | - brightGreen: fromCSS("--color-green-400"), |
104 | | - yellow: fromCSS("--color-yellow-700"), |
105 | | - brightYellow: fromCSS("--color-yellow-400"), |
106 | | - blue: fromCSS("--color-indigo-600"), |
107 | | - brightBlue: fromCSS("--color-indigo-400"), |
108 | | - magenta: fromCSS("--color-fuchsia-600"), |
109 | | - brightMagenta: fromCSS("--color-fuchsia-400"), |
110 | | - cyan: fromCSS("--color-cyan-600"), |
111 | | - brightCyan: fromCSS("--color-cyan-400"), |
112 | | - white: fromCSS("--color-base-100"), |
113 | | - brightWhite: fromCSS("--color-white"), |
114 | | - }, |
115 | | - }); |
116 | | - terminalInstanceRef.current = term; |
117 | | - |
118 | | - fitAddonRef.current = new FitAddon(); |
119 | | - term.loadAddon(fitAddonRef.current); |
120 | | - // fitAddon.fit(); |
121 | | - |
122 | | - term.open(terminalRef.current); |
123 | | - |
124 | | - // https://github.com/xtermjs/xterm.js/issues/2478 |
125 | | - // my.code();ではCtrl+Cでのkeyboardinterruptは要らないので、コピーペーストに置き換えてしまう |
126 | | - term.attachCustomKeyEventHandler((arg) => { |
127 | | - if ( |
128 | | - arg.ctrlKey && |
129 | | - (arg.key === "c" || arg.key === "x") && |
130 | | - arg.type === "keydown" |
131 | | - ) { |
132 | | - const selection = term.getSelection(); |
133 | | - if (selection) { |
134 | | - navigator.clipboard.writeText(selection); |
| 73 | + if (typeof window !== "undefined") { |
| 74 | + const abortController = new AbortController(); |
| 75 | + // globals.cssでフォントを指定し読み込んでいるが、 |
| 76 | + // それが読み込まれる前にterminalを初期化してしまうとバグる。 |
| 77 | + Promise.all([ |
| 78 | + import("@xterm/xterm"), |
| 79 | + import("@xterm/addon-fit"), |
| 80 | + document.fonts.load("0.875rem Inconsolata Variable"), |
| 81 | + ]).then(([{ Terminal }, { FitAddon }]) => { |
| 82 | + if (!abortController.signal.aborted) { |
| 83 | + const fromCSS = (varName: string) => |
| 84 | + window.getComputedStyle(document.body).getPropertyValue(varName); |
| 85 | + // "--color-" + color_name のように文字列を分割するとTailwindCSSが認識せずCSSの値として出力されない場合があるので注意 |
| 86 | + const term = new Terminal({ |
| 87 | + cursorBlink: true, |
| 88 | + convertEol: true, |
| 89 | + cursorStyle: "bar", |
| 90 | + cursorInactiveStyle: "none", |
| 91 | + fontSize: 14, |
| 92 | + lineHeight: 1.4, |
| 93 | + letterSpacing: 0, |
| 94 | + fontFamily: "'Inconsolata Variable','Noto Sans JP Variable'", |
| 95 | + theme: { |
| 96 | + // DaisyUIの変数を使用してテーマを設定している |
| 97 | + // TODO: ダークテーマ/ライトテーマを切り替えたときに再設定する? |
| 98 | + background: fromCSS("--color-base-300"), |
| 99 | + foreground: fromCSS("--color-base-content"), |
| 100 | + cursor: fromCSS("--color-base-content"), |
| 101 | + selectionBackground: fromCSS("--color-primary"), |
| 102 | + selectionForeground: fromCSS("--color-primary-content"), |
| 103 | + black: fromCSS("--color-black"), |
| 104 | + brightBlack: fromCSS("--color-neutral-500"), |
| 105 | + red: fromCSS("--color-red-600"), |
| 106 | + brightRed: fromCSS("--color-red-400"), |
| 107 | + green: fromCSS("--color-green-600"), |
| 108 | + brightGreen: fromCSS("--color-green-400"), |
| 109 | + yellow: fromCSS("--color-yellow-700"), |
| 110 | + brightYellow: fromCSS("--color-yellow-400"), |
| 111 | + blue: fromCSS("--color-indigo-600"), |
| 112 | + brightBlue: fromCSS("--color-indigo-400"), |
| 113 | + magenta: fromCSS("--color-fuchsia-600"), |
| 114 | + brightMagenta: fromCSS("--color-fuchsia-400"), |
| 115 | + cyan: fromCSS("--color-cyan-600"), |
| 116 | + brightCyan: fromCSS("--color-cyan-400"), |
| 117 | + white: fromCSS("--color-base-100"), |
| 118 | + brightWhite: fromCSS("--color-white"), |
| 119 | + }, |
| 120 | + }); |
| 121 | + terminalInstanceRef.current = term; |
| 122 | + |
| 123 | + fitAddonRef.current = new FitAddon(); |
| 124 | + term.loadAddon(fitAddonRef.current); |
| 125 | + // fitAddon.fit(); |
| 126 | + |
| 127 | + term.open(terminalRef.current); |
| 128 | + |
| 129 | + // https://github.com/xtermjs/xterm.js/issues/2478 |
| 130 | + // my.code();ではCtrl+Cでのkeyboardinterruptは要らないので、コピーペーストに置き換えてしまう |
| 131 | + term.attachCustomKeyEventHandler((arg) => { |
| 132 | + if ( |
| 133 | + arg.ctrlKey && |
| 134 | + (arg.key === "c" || arg.key === "x") && |
| 135 | + arg.type === "keydown" |
| 136 | + ) { |
| 137 | + const selection = term.getSelection(); |
| 138 | + if (selection) { |
| 139 | + navigator.clipboard.writeText(selection); |
| 140 | + return false; |
| 141 | + } |
| 142 | + } |
| 143 | + if (arg.ctrlKey && arg.key === "v" && arg.type === "keydown") { |
135 | 144 | return false; |
136 | 145 | } |
137 | | - } |
138 | | - if (arg.ctrlKey && arg.key === "v" && arg.type === "keydown") { |
139 | | - return false; |
140 | | - } |
141 | | - return true; |
142 | | - }); |
143 | | - |
144 | | - setTermReady(true); |
145 | | - onReadyRef.current?.(); |
146 | | - } |
147 | | - }); |
148 | | - |
149 | | - const observer = new ResizeObserver(() => { |
150 | | - // fitAddon.fit(); |
151 | | - const dims = fitAddonRef.current?.proposeDimensions(); |
152 | | - if (dims && !isNaN(dims.cols)) { |
153 | | - const rows = Math.max(5, getRowsRef.current?.(dims.cols) ?? 0); |
154 | | - terminalInstanceRef.current?.resize(dims.cols, rows); |
155 | | - } |
156 | | - }); |
157 | | - observer.observe(terminalRef.current); |
158 | | - |
159 | | - return () => { |
160 | | - abortController.abort("terminal component dismount"); |
161 | | - observer.disconnect(); |
162 | | - if (fitAddonRef.current) { |
163 | | - fitAddonRef.current.dispose(); |
164 | | - fitAddonRef.current = null; |
165 | | - } |
166 | | - if (terminalInstanceRef.current) { |
167 | | - terminalInstanceRef.current.dispose(); |
168 | | - terminalInstanceRef.current = null; |
169 | | - } |
170 | | - }; |
| 146 | + return true; |
| 147 | + }); |
| 148 | + |
| 149 | + setTermReady(true); |
| 150 | + onReadyRef.current?.(); |
| 151 | + } |
| 152 | + }); |
| 153 | + |
| 154 | + const observer = new ResizeObserver(() => { |
| 155 | + // fitAddon.fit(); |
| 156 | + const dims = fitAddonRef.current?.proposeDimensions(); |
| 157 | + if (dims && !isNaN(dims.cols)) { |
| 158 | + const rows = Math.max(5, getRowsRef.current?.(dims.cols) ?? 0); |
| 159 | + terminalInstanceRef.current?.resize(dims.cols, rows); |
| 160 | + } |
| 161 | + }); |
| 162 | + observer.observe(terminalRef.current); |
| 163 | + |
| 164 | + return () => { |
| 165 | + abortController.abort("terminal component dismount"); |
| 166 | + observer.disconnect(); |
| 167 | + if (fitAddonRef.current) { |
| 168 | + fitAddonRef.current.dispose(); |
| 169 | + fitAddonRef.current = null; |
| 170 | + } |
| 171 | + if (terminalInstanceRef.current) { |
| 172 | + terminalInstanceRef.current.dispose(); |
| 173 | + terminalInstanceRef.current = null; |
| 174 | + } |
| 175 | + }; |
| 176 | + } |
171 | 177 | }, []); |
172 | 178 |
|
173 | 179 | // テーマが変わったときにterminalのテーマを更新する |
|
0 commit comments