こんにちは、Androidエンジニア志望のHalkoです。
最近、Raycastというランチャーツールを使用し始めました。
Raycastは自分で拡張機能を保存できます。
そこで、普段Notionに記録している作業記録を、Raycastから保存できるような拡張機能を作ってみました。
準備
インストールすべきもの
- Raycast 1.26.0 またはそれ以上のバージョン
- Node.js 16.10 or またはそれ以上のバージョン
nvmのインストールが推奨されているようです - Visual Studio Codeをコードエディタとして使用します。
新しく拡張機能を作る
Raycastを開いて、create Extensionと入力します
作成画面が出てくるので、必要最低限の項目を埋めます。
- テンプレートには、Formを指定
- Extension Name, Location, Command Nameが必須項目
Create Extensionのコマンドを実行すると、テンプレートに沿った拡張機能ファイルが作成されます。
Open Manage Extensions というコマンドが出てきます。
実行すると、VSCodeが自動的に開きま拡張機能の一覧が確認できます。
EnterでVsCodeが自動的に開きますので、
ここから、テンプレートファイルを編集していきます。
ターミナルでの準備
開かれたファイルにある、index.tsxのimportに記載されている、@raycast/apiをインストールします。
また、今回はNotionのAPIも使用したいので、一緒にインストールしておきます。
ターミナルを開いて、
$ npm install @notionhq/client
$ npm install @raycast/api
また、拡張機能を実行できるようにするため
$ npm i
$ npm run dev
これで、ファイルを保存するたびに、自動でビルドして、変更された拡張機能を実行してくれるようになります。
いざ、コードの記入
今回は、
- NotionDBから一行分のデータを得る
- UIに表示する
- NotionDBの行をアップデートする
- NotionDBに新しく行を挿入する
というのが主な機能です。
出来上がったものは、こんな感じです。
APIを触るのも、TypeScriptを書くのも初めてだったので、新しく学んだことだらけでした。
全てはここに書ききれないので、最終的なindex.tsxのコードを挙げておきます。
import { Action, ActionPanel, Form ,LaunchProps} from '@raycast/api'
import { useRef} from "react";
import { getUIDataFromNotion } from './getUIDataFromNotion';
import { usePromise } from "@raycast/utils";
import { EntryValues } from './EntryValues';
const Demo = () => {
const abortable = useRef<AbortController>();
const { isLoading, data, revalidate } = usePromise(
async () => {
const result = await getUIDataFromNotion()
return result;
},[],
{abortable}
);
console.log("called")
return (
<Form
key={"form"}
isLoading ={isLoading}
actions={
<ActionPanel
key={"actionPanel"}
>
<Action.SubmitForm
key={"Action.SubmitForm"}
title={data?.submitTitle}
onSubmit={(values: EntryValues) => {
data?.doOnSubmit(values)
}}
/>
</ActionPanel>
}>
<Form.TextArea
key={"Form.TextArea"}
id="contentField"
title={data?.contentTitle}
value = {data?.openedRowData?.comment}
/>
<Form.Dropdown key={"Form.Dropdown"} id="tag" title="activity tag">
{data?.tagList.map((item:string)=>(
<Form.Dropdown.Item key={"Form.Dropdown.Item"+item} value={item} title={item} />
))}
</Form.Dropdown>
<Form.DatePicker
key={"Form.DatePicker"}
id="dateTime"
title={data?.dateTitle}
defaultValue={new Date()}
/>
</Form>
);
};
export default function Command(props: LaunchProps<{ draftValues: EntryValues }>) {
return Demo()
}
解説のポイントは、
usePromise
LaunchProps
です
usePromise
Notionからデータを得る、というように、
非同期処理でデータを得たい場合、返り値はPromise<欲しいクラス>となるようです。
async function getNotionData():Promise<NotionData>{
//Notionからデータを取ってくる
const response = await notion.databases.retrieve({database_id:databaseId})
const notionData = getNotionDataFromResponse(response)
return new Promise((resolve,reject)=>
resolve(notionData)
)
}
Promise<>の中のクラスにアクセスしたい場合、
await という処理をくっつけなければなりません。
厄介なことに、await処理は、非同期処理の中でしか呼べません。
//NotionDataにはアクセスできないので、エラーになる
const data:NotionData = getNotionData()
//awaitをつけると大丈夫
const data:NotionData = await getNotionData()
//非同期処理でないCommand()でawaitを呼ぶと、エラーになる
function Command(){
const data:NotionData = await getNotionData()
}
最終的にRaycastから呼ばれる、Command() のなかで、NotionDataを使いたいわけですが、
このCommand()は非同期処理(async function)にすることができません。
そこで、Raycastが提供しているusePromiseユーティリティを使用すると、
Command()を非同期処理にすることなく、awaitを使えるようになります!
const abortable = useRef<AbortController>();
//dataにアクセスすればNotionDataにアクセスできる!
const { isLoading, data, revalidate } = usePromise(
async () => {
const result = await getNotionData()
return result;
},[],
{abortable}
);
非同期処理が終わり、Promiseデータが返ってきた瞬間に、UIを更新してくれます。
LaunchProps
これは非常にシンプルな話です。
前述の通り私は、
Raycastの画面上に記入したテキストや日付を、Notionに送る機能
を作りたかったです。
そのためには、Notionにデータを送る、というコマンドが呼ばれた時、
UIに記入されたデータが必要でした。
そこで、Command()の引数に、LaunchPropsを追加しました。
interface EntryValues {
dateTime: Date ;
contentField: string;
tag: string;
}
export default function Command() {
return Demo()
}
//変更後
export default function Command(props: LaunchProps<{ draftValues: EntryValues }>) {
return Demo()
}
EntryValues
は自分で作成したinterfaceなので、自分が作ったUIに応じて変更してください。
LaunchPropsを追加した後、
UIのデータにアクセスしたい際のコマンドを書き換えます。
<Action.SubmitForm
key={"Action.SubmitForm"}
title={data?.submitTitle}
onSubmit={
//EntryValuesをとってくる
(values: EntryValues) => {
//valuesの中には、記入したデータ等が入るようになる。
doSomethingWithEntryValues(values)
}}
/>
これで、UIで記入したデータ等を利用できるようになります。
参考リンク