wingsfly

wingsfly

Observer

使用CopilotによるTauriの開発

環境#

システム:Windows 11
IDE:VSCode + Github Copilot Chat + Rust + Tauri プラグイン
環境:npm、Rust
npm create tauri-app@latestを使用してプロジェクトフレームワークを構築しました。
以下のタイトルはすべて VSCode の Copilot チャットに入力されたテキストであり、その後 Copilot が出力したコードを更新して置き換えました。


ホームページを次のように変更します:左側にナビゲーションエリアを持ち、ツリー状のビューで異なる機能のページリンクを表示します。左側のナビゲーションエリアの機能メニュー項目をクリックすると、右側に対応する機能ページが読み込まれます。#

Note

ここでは 'App.tsx' が引用されています。


GitHub Copilot: この機能を実現するために、App.tsxで以下の手順を実行する必要があります:

  1. ナビゲーションバーコンポーネントを作成し、ツリー状のビューで異なる機能のページリンクを表示します。
  2. ページナビゲーションを処理するために React Router を使用します。
  3. 右側のエリアに対応する機能ページを読み込みます。

まず、React Router をインストールします:

npm install react-router-dom

次に、App.tsxファイルを以下のように変更します:

import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import reactLogo from "./assets/react.svg";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";

function Home() {
  return <h2>ホームページ</h2>;
}

function Feature1() {
  return <h2>機能1ページ</h2>;
}

function Feature2() {
  return <h2>機能2ページ</h2>;
}

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

同時に、新しいレイアウトに適応するためにApp.cssファイルを更新します:

.container {
  display: flex;
}

.sidebar {
  width: 200px;
  background-color: #f0f0f0;
  padding: 10px;
}

.content {
  flex-grow: 1;
  padding: 10px;
}

これにより、左側にナビゲーションバーが表示され、右側にはナビゲーションバーの選択に応じて対応する機能ページが読み込まれます。

Similar code found with 1 license type


独立した Home コンポーネントを作成します#


GitHub Copilot: はい、Homeコンポーネントを独立したファイルに抽出できます。以下は手順です:

  1. 新しいファイルHome.tsxを作成します。
  2. HomeコンポーネントのコードをHome.tsxファイルに移動します。
  3. App.tsxHomeコンポーネントをインポートします。

まず、Home.tsxファイルを作成し、以下のコードを追加します:

// Home.tsx
import React from "react";

function Home() {
  return <h2>ホームページ</h2>;
}

export default Home;

次に、App.tsxファイルを変更し、Homeコンポーネントをインポートします:

import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import reactLogo from "./assets/react.svg";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";

function Feature1() {
  return <h2>機能1ページ</h2>;
}

function Feature2() {
  return <h2>機能2ページ</h2>;
}

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これで、Homeコンポーネントが独立したファイルに抽出されました。

Similar code found with 1 license type


左側のナビゲーションバーを垂直に引き伸ばしてページ全体を占めるようにします#

Note

左側のナビゲーションバーはテキストを含む領域しか占有していないため、全体を占めるようにしたいです。


GitHub Copilot: 左側のナビゲーションバーをページ全体に垂直に引き伸ばすために、CSS スタイルを調整する必要があります。具体的な手順は以下の通りです:

  1. App.cssファイルを変更し、ナビゲーションバーがページの高さを占めるようにします。
  2. コンテナの高さもページ全体を占めるようにします。

以下は更新されたApp.cssファイルです:

/* App.css */
html, body, #root {
  height: 100%;
  margin: 0;
  padding: 0;
}

.container {
  display: flex;
  height: 100%;
}

.sidebar {
  width: 200px;
  background-color: #f0f0f0;
  padding: 10px;
  height: 100%;
}

.content {
  flex-grow: 1;
  padding: 10px;
}

App.tsxファイルに以下のコードが含まれていることを確認してください:

import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";

function Feature1() {
  return <h2>機能1ページ</h2>;
}

function Feature2() {
  return <h2>機能2ページ</h2>;
}

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これにより、左側のナビゲーションバーがページの高さを垂直に引き伸ばすようになります。

Similar code found with 1 license type


右側のページと左側のナビゲーションバーを上部で揃えます#

Note

右側のコンテンツエリアがナビゲーションエリアの下に表示される状況が発生しました。4、5、6 は修正を試みたプロンプトであり、CSS の.containerスタイルが完全に置き換えられていないことがわかり、再度置き換えた後に正常に表示されました。


GitHub Copilot: 右側のページと左側のナビゲーションバーを上部で揃えるために、CSS スタイルを調整する必要があります。具体的な手順は以下の通りです:

  1. コンテナの高さがページ全体を占め、ナビゲーションバーとコンテンツエリアが上部から揃っていることを確認します。
  2. レイアウトが正しいことを確認するためにApp.cssファイルを更新します。

以下は更新されたApp.cssファイルです:

/* App.css */
html, body, #root {
  height: 100%;
  margin: 0;
  padding: 0;
}

.container {
  display: flex;
  height: 100%;
  align-items: flex-start; /* 子要素を上部で揃える */
}

.sidebar {
  width: 200px;
  background-color: #f0f0f0;
  padding: 10px;
  height: 100%;
}

.content {
  flex-grow: 1;
  padding: 10px;
}

App.tsxファイルに以下のコードが含まれていることを確認してください:

import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";

function Feature1() {
  return <h2>機能1ページ</h2>;
}

function Feature2() {
  return <h2>機能2ページ</h2>;
}

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これにより、右側のページと左側のナビゲーションバーが上部で揃います。

Similar code found with 1 license type


.container 内の子要素を左右に配置します#


GitHub Copilot: .container内の子要素を左右に配置するために、CSS スタイルを調整する必要があります。具体的な手順は以下の通りです:

  1. .containerflexレイアウトを使用し、子要素が左右に配置されることを確認します。
  2. 重複した.containerスタイル定義を削除します。

以下は更新されたApp.cssファイルです:

/* App.css */
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;

html, body, #root {
  height: 100%;
  margin: 0;
  padding: 0;
}

.container {
  display: flex;
  height: 100%;
  align-items: flex-start; /* 子要素を上部で揃える */
}

.sidebar {
  width: 200px;
  background-color: #f0f0f0;
  padding: 10px;
  height: 100%;
}

.content {
  flex-grow: 1;
  padding: 10px;
}

App.tsxファイルに以下のコードが含まれていることを確認してください:

import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";

function Feature1() {
  return <h2>機能1ページ</h2>;
}

function Feature2() {
  return <h2>機能2ページ</h2>;
}

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これにより、.container内の子要素が左右に配置され、上部で揃います。

Similar code found with 1 license type


ナビゲーションバーの背景色をシステムの背景色に設定します#

Note

システムテーマは暗色ですが、デフォルトの背景色は白色であり、このコードは実際には機能しませんでした。最終的に.sidebarの背景色設定を手動で削除しました。


GitHub Copilot: ナビゲーションバーの背景色をシステムの背景色に設定するために、CSS のbackground-colorプロパティを使用し、その値をbackgroundシステムカラーに設定します。以下は更新されたApp.cssファイルです:

/* App.css */
html, body, #root {
  height: 100%;
  margin: 0;
  padding: 0;
}

.container {
  display: flex;
  height: 100%;
  align-items: flex-start; /* 子要素を上部で揃える */
}

.sidebar {
  width: 200px;
  background-color: var(--system-background-color, #f0f0f0); /* システム背景色を使用 */
  padding: 10px;
  height: 100%;
}

.content {
  flex-grow: 1;
  padding: 10px;
}

.logo {
  height: 6em;
}

上記のコードでは、var(--system-background-color, #f0f0f0)を使用して背景色を設定しています。システム背景色が未定義の場合は、デフォルトの色#f0f0f0を使用します。必要に応じてデフォルトの色を調整できます。


Feature1 を独立したモジュールとして Features サブディレクトリに移動します#


GitHub Copilot: はい、Feature1コンポーネントを独立したファイルに抽出し、Featuresサブディレクトリに配置できます。以下は手順です:

  1. 新しいディレクトリFeaturesを作成します。
  2. Featuresディレクトリ内に新しいファイルFeature1.tsxを作成します。
  3. Feature1コンポーネントのコードをFeature1.tsxファイルに移動します。
  4. App.tsxFeature1コンポーネントをインポートします。

まず、Features/Feature1.tsxファイルを作成し、以下のコードを追加します:

// Features/Feature1.tsx
import React from "react";

function Feature1() {
  return <h2>機能1ページ</h2>;
}

export default Feature1;

次に、App.tsxファイルを変更し、Feature1コンポーネントをインポートします:

import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";

function Feature2() {
  return <h2>機能2ページ</h2>;
}

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これにより、Feature1コンポーネントがFeaturesサブディレクトリの独立したモジュールに移動されました。

Similar code found with 1 license type


同様に Feature2 を独立したファイルに移動します#


GitHub Copilot: はい、Feature2コンポーネントを独立したファイルに抽出し、Featuresサブディレクトリに配置できます。以下は手順です:

  1. Featuresディレクトリ内に新しいファイルFeature2.tsxを作成します。
  2. Feature2コンポーネントのコードをFeature2.tsxファイルに移動します。
  3. App.tsxFeature2コンポーネントをインポートします。

まず、Features/Feature2.tsxファイルを作成し、以下のコードを追加します:

// Features/Feature2.tsx
import React from "react";

function Feature2() {
  return <h2>機能2ページ</h2>;
}

export default Feature2;

次に、App.tsxファイルを変更し、Feature2コンポーネントをインポートします:

import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";
import Feature2 from "./Features/Feature2";

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これにより、Feature2コンポーネントがFeaturesサブディレクトリの独立したモジュールに移動されました。

Similar code found with 1 license type


ページのデフォルトにスクロールバーが表示されないように高さを調整します#

Note

デフォルトのコンテンツエリアにスクロールバーが表示されました。


GitHub Copilot: ページの高さを調整してスクロールバーが表示されないようにするために、コンテナの高さとコンテンツの高さが正しく設定されていることを確認する必要があります。overflow: hiddenを使用してスクロールバーの表示を防ぎ、コンテンツエリアの高さがビューポートの高さを超えないようにします。

以下は更新されたApp.cssファイルです:

/* App.css */
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;

html, body, #root {
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden; /* スクロールバーの表示を防ぐ */
}

.container {
  display: flex;
  height: 100%;
  align-items: flex-start; /* 子要素を上部で揃える */
}

.sidebar {
  width: 200px;
  padding: 10px;
  height: 100%;
  background-color: var(--system-background-color, #f0f0f0); /* システム背景色を使用 */
}

.content {
  flex-grow: 1;
  padding: 10px;
  height: 100%;
  overflow: auto; /* コンテンツエリアをスクロール可能にする */
}

App.tsxファイルに以下のコードが含まれていることを確認してください:

import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";
import Feature2 from "./Features/Feature2";

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これにより、ページの高さが調整され、スクロールバーが表示されないようになります。コンテンツエリアはスクロール可能です。

Similar code found with 1 license type


greetMsg の段落を新しい行に表示します#

Note

ここでの理由は、例の中で greet のインタラクションを保持するために、手動でコードをApp.tsxからHome.tsxに移動しましたが、<p>タグが機能せず、テキスト出力エリアが同じ行に表示されました。次のプロンプトで、<div>タグを使用して問題を解決しました。


GitHub Copilot: greetMsgを新しい行に表示するために、段落 (<p>) タグに入れることができます。以下は更新されたHome.tsxファイルです:

// Home.tsx
import React from "react";

function Home({ greetMsg }: { greetMsg: string }) {
  return (
    <div>
      <h2>ホームページ</h2>
      <p>{greetMsg}</p>
    </div>
  );
}

export default Home;

同時に、App.tsxgreetMsgHomeコンポーネントのプロパティとして渡すことを確認します:

import { useState } from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";
import Feature2 from "./Features/Feature2";

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home greetMsg={greetMsg} />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これにより、greetMsgHomeコンポーネントの新しい行に表示されます。

Similar code found with 1 license type


greetMsg は App から渡す必要はありません#


GitHub Copilot: はい、Homeコンポーネント内で直接greetMsgを定義し、Appから渡す必要はありません。以下は更新されたHome.tsxファイルです:

// Home.tsx
import React, { useState } from "react";
import { invoke } from "@tauri-apps/api/core";

function Home() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <div>
      <h2>ホームページ</h2>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="名前を入力してください"
      />
      <button onClick={greet}>挨拶</button>
      <p>{greetMsg}</p>
    </div>
  );
}

export default Home;

同時に、App.tsxファイルは変更する必要がありません。なぜなら、greetMsgはもはやAppから渡す必要がないからです:

import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import "./App.css";
import Home from "./Home";
import Feature1 from "./Features/Feature1";
import Feature2 from "./Features/Feature2";

function App() {
  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/feature1">機能1</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/feature1" element={<Feature1 />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これにより、greetMsgHomeコンポーネント内で定義され、使用されます。

Similar code found with 2 license types


Feature1 を ClipBoard にリネームします#


GitHub Copilot: はい、Feature1コンポーネントをClipBoardにリネームできます。以下は手順です:

  1. Feature1.tsxファイルの名前をFeatures/ClipBoard.tsxに変更します。
  2. コンポーネント名をClipBoardに更新します。
  3. App.tsxClipBoardコンポーネントをインポートします。

まず、Feature1.tsxファイルの名前をFeatures/ClipBoard.tsxに変更し、ファイル内容を以下のように更新します:

// Features/ClipBoard.tsx
import React from "react";

function ClipBoard() {
  return <h2>クリップボードページ</h2>;
}

export default ClipBoard;

次に、App.tsxファイルを変更し、ClipBoardコンポーネントをインポートします:

import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import "./App.css";
import Home from "./Home";
import ClipBoard from "./Features/ClipBoard";
import Feature2 from "./Features/Feature2";

function App() {
  return (
    <Router>
      <div className="container">
        <nav className="sidebar">
          <ul>
            <li>
              <Link to="/">ホーム</Link>
            </li>
            <li>
              <Link to="/clipboard">クリップボード</Link>
            </li>
            <li>
              <Link to="/feature2">機能2</Link>
            </li>
          </ul>
        </nav>
        <div className="content">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/clipboard" element={<ClipBoard />} />
            <Route path="/feature2" element={<Feature2 />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
}

export default App;

これにより、Feature1コンポーネントがClipBoardにリネームされ、App.tsxで正しくインポートされ、使用されます。


クリップボードページに最近のクリップボード内容を表示し、時間の逆順で並べ、最大 100 件を表示します#


GitHub Copilot: クリップボードページに最近のクリップボード内容を表示し、時間の逆順で並べ、最大 100 件を表示するために、以下の手順が必要です:

  1. クリップボード内容を保存するための状態を作成します。
  2. useEffectフックを使用してクリップボード内容を取得するシミュレーションを行います。
  3. クリップボード内容を時間の逆順で並べ、最大 100 件を制限します。

以下は更新されたClipBoard.tsxファイルです:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";

interface ClipboardItem {
  content: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);

  useEffect(() => {
    // クリップボード内容を取得するシミュレーション
    const fetchClipboardItems = () => {
      const items = [
        { content: "アイテム1", timestamp: Date.now() - 1000 },
        { content: "アイテム2", timestamp: Date.now() - 2000 },
        // さらにシミュレーションデータを追加
      ];
      setClipboardItems(items);
    };

    fetchClipboardItems();
  }, []);

  // 時間の逆順で並べ、最大100件を制限
  const sortedItems = clipboardItems
    .sort((a, b) => b.timestamp - a.timestamp)
    .slice(0, 100);

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {sortedItems.map((item, index) => (
          <li key={index}>
            <p>{item.content}</p>
            <small>{new Date(item.timestamp).toLocaleString()}</small>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

この実装では、以下のことを行います:

  1. クリップボード内容項目を表すためにClipboardItemインターフェースを定義します。
  2. useStateフックを使用してクリップボード内容を保存します。
  3. useEffectフックを使用してクリップボード内容を取得するシミュレーションを行い、状態に保存します。
  4. クリップボード内容を時間の逆順で並べ、最大 100 件を制限します。
  5. コンポーネント内でクリップボード内容リストをレンダリングします。

必要に応じて、シミュレーションデータ部分を実際のクリップボード内容を取得するように置き換えることができます。


1 秒ごとにクリップボードを定期的に読み取り、新しい内容がある場合はクリップボードの内容リストに追加します#

Note

ここでは、src-tauri/capabilities/default.jsonにクリップボードの読み書き権限を手動で追加する必要があります。以降のファイル操作も同様です。また、copilot コード内のimportは Tauri v1 の形式で出力されているため、手動で v2 の形式に調整する必要があります。


GitHub Copilot: 1 秒ごとにクリップボードを定期的に読み取り、新しい内容がある場合はクリップボードの内容リストに追加するために、setIntervalを使用してクリップボード内容を定期的に読み取り、useEffect内で処理を行います。

以下は更新されたClipBoard.tsxファイルです:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText } from "@tauri-apps/plugin-clipboard";

interface ClipboardItem {
  content: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        setClipboardItems((prevItems) => [
          { content, timestamp: Date.now() },
          ...prevItems,
        ]);
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  // 時間の逆順で並べ、最大100件を制限
  const sortedItems = clipboardItems.slice(0, 100);

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {sortedItems.map((item, index) => (
          <li key={index}>
            <p>{item.content}</p>
            <small>{new Date(item.timestamp).toLocaleString()}</small>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

この実装では、以下のことを行います:

  1. useStateフックを使用してクリップボード内容と最後に読み取った内容を保存します。
  2. useEffectフックを使用して 1 秒ごとにクリップボード内容を読み取るタイマーを設定します。
  3. 新しい内容が最後に読み取った内容と異なる場合、クリップボード内容リストに追加し、最後に読み取った内容を更新します。
  4. 時間の逆順で並べ、最大 100 件を制限します。
  5. コンポーネント内でクリップボード内容リストをレンダリングします。

これにより、1 秒ごとにクリップボード内容を読み取り、新しい内容がある場合はクリップボードの内容リストに追加されます。


時間を内容の上に表示します#


GitHub Copilot: 時間を内容の上に表示するために、ClipBoard.tsxファイル内のレンダリング順序を調整する必要があります。以下は更新されたコードです:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText } from "@tauri-apps/plugin-clipboard";

interface ClipboardItem {
  content: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        setClipboardItems((prevItems) => [
          { content, timestamp: Date.now() },
          ...prevItems,
        ]);
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  // 時間の逆順で並べ、最大100件を制限
  const sortedItems = clipboardItems.slice(0, 100);

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {sortedItems.map((item, index) => (
          <li key={index}>
            <small>{new Date(item.timestamp).toLocaleString()}</small>
            <p>{item.content}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

この実装では、時間 (<small>{new Date(item.timestamp).toLocaleString()}</small>) を内容 (<p>{item.content}</p>) の上に配置しました。これにより、時間が内容の上に表示されます。


各項目に「コピー」と「削除」のテキストリンクを提供し、コピーをクリックすると再度クリップボードにコピーし、削除をクリックするとその項目をリストから削除します#


GitHub Copilot: 各項目に「コピー」と「削除」のテキストリンクを提供するために、以下の手順が必要です:

  1. 「コピー」と「削除」ボタンを追加します。
  2. コピー機能を実装し、内容を再度クリップボードにコピーします。
  3. 削除機能を実装し、リストからその項目を削除します。

以下は更新されたClipBoard.tsxファイルです:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";

interface ClipboardItem {
  content: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        setClipboardItems((prevItems) => [
          { content, timestamp: Date.now() },
          ...prevItems,
        ]);
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  const handleCopy = async (content: string) => {
    await writeText(content);
  };

  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };

  // 時間の逆順で並べ、最大100件を制限
  const sortedItems = clipboardItems.slice(0, 100);

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {sortedItems.map((item, index) => (
          <li key={index}>
            <small>{new Date(item.timestamp).toLocaleString()}</small>
            <p>{item.content}</p>
            <a href="#" onClick={() => handleCopy(item.content)}>コピー</a> | 
            <a href="#" onClick={() => handleDelete(item.timestamp)}>削除</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

この実装では、以下のことを行います:

  1. 「コピー」と「削除」ボタンを追加しました。
  2. handleCopy関数を実装し、内容を再度クリップボードにコピーします。
  3. handleDelete関数を実装し、リストからその項目を削除します。
  4. 各リスト項目に「コピー」と「削除」リンクを追加し、対応するイベント処理関数をバインドします。

これにより、各項目に「コピー」と「削除」のテキストリンクが提供され、コピーをクリックすると再度クリップボードにコピーされ、削除をクリックするとリストからその項目が削除されます。


リストに内容のテキストと内容の md5 値を同時に保存し、新しい内容の md5 値が既存の内容のテキストと一致する場合は、単に時間を更新し、新しいレコードを追加しないようにします#

Note

コピー後に新しいレコードが生成されるため、md5 を使用して一意性を確保し、表示を行います。


GitHub Copilot: リストに内容のテキストと内容の MD5 値を同時に保存し、新しい内容の MD5 値が既存の内容のテキストと一致する場合は、単に時間を更新し、新しいレコードを追加しないようにするために、以下の手順が必要です:

  1. crypto-jsライブラリをインストールして MD5 値を計算します。
  2. ClipboardItemインターフェースを更新して MD5 値を含めます。
  3. クリップボード内容を読み取る際に MD5 値を計算し、既存の MD5 値と一致するかどうかを確認します。

まず、crypto-jsライブラリをインストールします:

npm install crypto-js

次に、ClipBoard.tsxファイルを更新します:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";

interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const md5 = CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = prevItems.find(item => item.md5 === md5);
          if (existingItem) {
            return prevItems.map(item =>
              item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
            );
          } else {
            return [{ content, md5, timestamp: Date.now() }, ...prevItems];
          }
        });
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  const handleCopy = async (content: string) => {
    await writeText(content);
  };

  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };

  // 時間の逆順で並べ、最大100件を制限
  const sortedItems = clipboardItems.slice(0, 100);

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {sortedItems.map((item, index) => (
          <li key={index}>
            <small>{new Date(item.timestamp).toLocaleString()}</small>
            <p>{item.content}</p>
            <a href="#" onClick={() => handleCopy(item.content)}>コピー</a> | 
            <a href="#" onClick={() => handleDelete(item.timestamp)}>削除</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

この実装では、以下のことを行います:

  1. crypto-jsライブラリをインストールして MD5 値を計算します。
  2. ClipboardItemインターフェースを更新してmd5属性を含めます。
  3. クリップボード内容を読み取る際に MD5 値を計算し、既存の MD5 値と一致するかどうかを確認します。一致する場合は時間を更新し、一致しない場合は新しいレコードを追加します。

同じ内容の更新後、リストを再度時間でソートします#


GitHub Copilot: 同じ内容の更新後、リストを再度時間でソートするために、更新されたタイムスタンプ後にリストをソートする必要があります。以下は更新されたClipBoard.tsxファイルです:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";

interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const md5 = CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = prevItems.find(item => item.md5 === md5);
          if (existingItem) {
            const updatedItems = prevItems.map(item =>
              item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
            );
            return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
          } else {
            const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
            return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
          }
        });
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  const handleCopy = async (content: string) => {
    await writeText(content);
  };

  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {clipboardItems.map((item, index) => (
          <li key={index}>
            <small>{new Date(item.timestamp).toLocaleString()}</small>
            <p>{item.content}</p>
            <a href="#" onClick={() => handleCopy(item.content)}>コピー</a> | 
            <a href="#" onClick={() => handleDelete(item.timestamp)}>削除</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

この実装では、更新されたタイムスタンプ後にリストをソートします。新しい内容が追加されると、リストは時間で再度ソートされます。


ソート後、ページも新しい順序で再表示されるようにします#

Note

ソート後、ページが再表示されないため、強制的に再表示するように要求されました。


GitHub Copilot: ソート後にページが新しい順序で再表示されるようにするために、setClipboardItems内でリストをソートし、レンダリング時にソートされたリストを使用する必要があります。

以下は更新されたClipBoard.tsxファイルです:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";

interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const md5 = CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = prevItems.find(item => item.md5 === md5);
          if (existingItem) {
            const updatedItems = prevItems.map(item =>
              item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
            );
            return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
          } else {
            const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
            return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
          }
        });
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  const handleCopy = async (content: string) => {
    await writeText(content);
  };

  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {clipboardItems.map((item, index) => (
          <li key={index}>
            <small>{new Date(item.timestamp).toLocaleString()}</small>
            <p>{item.content}</p>
            <a href="#" onClick={() => handleCopy(item.content)}>コピー</a> | 
            <a href="#" onClick={() => handleDelete(item.timestamp)}>削除</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

この実装では、setClipboardItems内でリストをソートし、レンダリング時にソートされたclipboardItemsリストを使用します。これにより、リストが再度ソートされた後、ページが新しい順序で再表示されます。


隣接する 2 行を異なる背景色で区別します#

Note

UI の最適化。


GitHub Copilot: 隣接する 2 行を異なる背景色で区別するために、CSS スタイルを使用してストライプ効果を実現します。以下は更新されたClipBoard.tsxファイルと対応する CSS スタイルです:

まず、ClipBoard.tsxファイルを更新します:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // CSSファイルをインポート

interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const md5 = CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = prevItems.find(item => item.md5 === md5);
          if (existingItem) {
            const updatedItems = prevItems.map(item =>
              item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
            );
            return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
          } else {
            const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
            return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
          }
        });
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  const handleCopy = async (content: string) => {
    await writeText(content);
  };

  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {clipboardItems.map((item, index) => (
          <li key={index} className={index % 2 === 0 ? "even" : "odd"}>
            <small>{new Date(item.timestamp).toLocaleString()}</small>
            <p>{item.content}</p>
            <a href="#" onClick={() => handleCopy(item.content)}>コピー</a> | 
            <a href="#" onClick={() => handleDelete(item.timestamp)}>削除</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

次に、ClipBoard.cssファイルを作成し、以下のスタイルを追加します:

/* ClipBoard.css */
ul {
  list-style-type: none;
  padding: 0;
}

li.even {
  background-color: #f9f9f9;
}

li.odd {
  background-color: #e9e9e9;
}

li {
  padding: 10px;
  margin: 5px 0;
}

a {
  margin-right: 10px;
}

この実装では、以下のことを行います:

  1. ClipBoard.tsxファイルでClipBoard.cssファイルをインポートします。
  2. 隣接する 2 行に異なる CSS クラス(evenodd)を追加して、異なる背景色を適用します。
  3. ClipBoard.cssファイルでevenoddクラスの背景色を定義します。

これにより、隣接する 2 行が異なる背景色で区別されます。


隣接する 2 行ではなく、隣接する 2 つのクリップボード内容を異なる色で区別します。テキスト内容、時間、操作ボタンを含みます#


GitHub Copilot: 隣接する 2 つのクリップボード内容を異なる色で区別するために、CSS スタイルを使用してストライプ効果を実現します。以下は更新されたClipBoard.tsxファイルと対応する CSS スタイルです:

まず、ClipBoard.tsxファイルを更新します:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // CSSファイルをインポート

interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const md5 = CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = prevItems.find(item => item.md5 === md5);
          if (existingItem) {
            const updatedItems = prevItems.map(item =>
              item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
            );
            return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
          } else {
            const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
            return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
          }
        });
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  const handleCopy = async (content: string) => {
    await writeText(content);
  };

  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {clipboardItems.map((item, index) => (
          <li key={index} className={index % 2 === 0 ? "even" : "odd"}>
            <small>{new Date(item.timestamp).toLocaleString()}</small>
            <p>{item.content}</p>
            <div>
              <a href="#" onClick={() => handleCopy(item.content)}>コピー</a> | 
              <a href="#" onClick={() => handleDelete(item.timestamp)}>削除</a>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

次に、ClipBoard.cssファイルを作成または更新し、以下のスタイルを追加します:

/* ClipBoard.css */
ul {
  list-style-type: none;
  padding: 0;
}

li.even {
  background-color: #f9f9f9;
}

li.odd {
  background-color: #e9e9e9;
}

li {
  padding: 10px;
  margin: 5px 0;
}

a {
  margin-right: 10px;
}

この実装では、以下のことを行います:

  1. ClipBoard.tsxファイルでClipBoard.cssファイルをインポートします。
  2. 隣接する 2 つのクリップボード内容に異なる CSS クラス(evenodd)を追加して、異なる背景色を適用します。
  3. ClipBoard.cssファイルでevenoddクラスの背景色を定義します。

これにより、隣接する 2 つのクリップボード内容が異なる背景色で区別され、テキスト内容、時間、操作ボタンが含まれます。


内容の背景色調を暗色調に調整し、テキストを対比度の高い色に調整します#


GitHub Copilot: 内容の背景色調を暗色調に調整し、テキストを対比度の高い色に調整するために、ClipBoard.cssファイルのスタイルを更新します。以下は更新されたClipBoard.cssファイルです:

/* ClipBoard.css */
ul {
  list-style-type: none;
  padding: 0;
}

li.even {
  background-color: #333; /* 暗色調背景 */
  color: #fff; /* 高対比度テキストカラー */
}

li.odd {
  background-color: #444; /* 暗色調背景 */
  color: #fff; /* 高対比度テキストカラー */
}

li {
  padding: 10px;
  margin: 5px 0;
}

a {
  margin-right: 10px; /* 高対比度リンクカラー */
  color: #1e90ff; /* 高対比度リンクカラー */
}

a:hover {
  text-decoration: underline;
}

この実装では、以下のことを行います:

  1. li.evenli.oddの背景色をそれぞれ#333#444に設定し、暗色調の背景を実現します。
  2. li.evenli.oddのテキスト色を#fffに設定し、高対比度のテキスト色を実現します。
  3. リンクの色を#1e90ffに設定し、高対比度のリンク色を実現し、ホバー時に下線を追加します。

これにより、内容の背景色調が暗色調に調整され、テキストが対比度の高い色に調整されます。


時間と操作ボタンを 1 行にまとめます#


GitHub Copilot: 時間と操作ボタンを 1 行にまとめるために、CSS Flexbox レイアウトを使用します。以下は更新されたClipBoard.tsxファイルと対応する CSS スタイルです:

まず、ClipBoard.tsxファイルを更新します:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // CSSファイルをインポート

interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const md5 = CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = prevItems.find(item => item.md5 === md5);
          if (existingItem) {
            const updatedItems = prevItems.map(item =>
              item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
            );
            return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
          } else {
            const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
            return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
          }
        });
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  const handleCopy = async (content: string) => {
    await writeText(content);
  };

  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {clipboardItems.map((item, index) => (
          <li key={index} className={index % 2 === 0 ? "even" : "odd"}>
            <div className="item-header">
              <small>{new Date(item.timestamp).toLocaleString()}</small>
              <div>
                <a href="#" onClick={() => handleCopy(item.content)}>コピー</a> | 
                <a href="#" onClick={() => handleDelete(item.timestamp)}>削除</a>
              </div>
            </div>
            <p>{item.content}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ClipBoard;

次に、ClipBoard.cssファイルを更新し、以下のスタイルを追加します:

/* ClipBoard.css */
ul {
  list-style-type: none;
  padding: 0;
}

li.even {
  background-color: #333; /* 暗色調背景 */
  color: #fff; /* 高対比度テキストカラー */
}

li.odd {
  background-color: #444; /* 暗色調背景 */
  color: #fff; /* 高対比度テキストカラー */
}

li {
  padding: 10px;
  margin: 5px 0;
}

.item-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

a {
  margin-right: 10px; /* 高対比度リンクカラー */
  color: #1e90ff; /* 高対比度リンクカラー */
}

a:hover {
  text-decoration: underline;
}

この実装では、以下のことを行います:

  1. ClipBoard.tsxファイルで時間と操作ボタンを 1 つのdivコンテナに配置し、item-headerクラスを追加します。
  2. ClipBoard.cssファイルでitem-headerクラスを定義し、Flexbox レイアウトを使用して時間と操作ボタンを 1 行にまとめ、両端に配置します。

これにより、時間と操作ボタンが 1 行にまとめられ、内容の背景色調が暗色調に調整され、テキストが対比度の高い色に調整されます。


1 分間隔で現在の内容リストと時間情報を appdata 下の history.json ファイルに保存します#

Note

履歴を永続的に保存します。ここでの読み書き API も同様に v1 の形式で出力されているため、手動で v2 の API 呼び出し方式に調整する必要があります。VSCode の tauri プラグインを使用すると、Ctrl + クリックで関数名を開いて API のソースコードを表示でき、その注釈に呼び出しの参考が示されています。


GitHub Copilot: 1 分間隔で現在の内容リストと時間情報をappdata下のhistory.jsonファイルに保存するために、以下の手順が必要です:

  1. fsモジュールを使用してファイルに書き込みます。
  2. setIntervalを使用して内容リストを定期的に保存します。
  3. コンポーネントがアンマウントされるときにタイマーをクリアします。

以下は更新されたClipBoard.tsxファイルです:

// Features/ClipBoard.tsx
import React, { useState, useEffect } from "react";
import { readText, writeText } from "@tauri-apps/plugin-clipboard-manager";
import { writeTextFile, BaseDirectory } from "@tauri-apps/api/fs";
import CryptoJS from "crypto-js";
import "./ClipBoard.css"; // CSSファイルをインポート

interface ClipboardItem {
  content: string;
  md5: string;
  timestamp: number;
}

function ClipBoard() {
  const [clipboardItems, setClipboardItems] = useState<ClipboardItem[]>([]);
  const [lastContent, setLastContent] = useState<string>("");

  useEffect(() => {
    const loadHistory = async () => {
      try {
        const data = await readTextFile("history.json", { dir: BaseDirectory.AppData });
        const items: ClipboardItem[] = JSON.parse(data);
        setClipboardItems(items);
      } catch (error) {
        console.error("履歴の読み込みに失敗しました:", error);
      }
    };

    loadHistory();
  }, []);

  useEffect(() => {
    const interval = setInterval(async () => {
      const content = await readText();
      if (content && content !== lastContent) {
        const md5 = CryptoJS.MD5(content).toString();
        setClipboardItems((prevItems) => {
          const existingItem = prevItems.find(item => item.md5 === md5);
          if (existingItem) {
            const updatedItems = prevItems.map(item =>
              item.md5 === md5 ? { ...item, timestamp: Date.now() } : item
            );
            return updatedItems.sort((a, b) => b.timestamp - a.timestamp);
          } else {
            const newItems = [{ content, md5, timestamp: Date.now() }, ...prevItems];
            return newItems.slice(0, 100).sort((a, b) => b.timestamp - a.timestamp);
          }
        });
        setLastContent(content);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [lastContent]);

  useEffect(() => {
    const saveInterval = setInterval(async () => {
      const data = JSON.stringify(clipboardItems, null, 2);
      await writeTextFile({ path: "history.json", contents: data }, { dir: BaseDirectory.AppData });
    }, 60000); // 1分間隔

    return () => clearInterval(saveInterval);
  }, [clipboardItems]);

  const handleCopy = async (content: string) => {
    await writeText(content);
  };

  const handleDelete = (timestamp: number) => {
    setClipboardItems((prevItems) =>
      prevItems.filter((item) => item.timestamp !== timestamp)
    );
  };

  return (
    <div>
      <h2>クリップボードページ</h2>
      <ul>
        {clipboardItems.map((item, index) => (
          <li key={
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。