Open16

Fresh (とDeno) あれこれ

nikogolinikogoli

アイコン

Tabler Icons は↓からコピって使う。なんか v.0.2 になってパワーアップしてる
https://tabler-icons-tsx.deno.dev/
Tabler Icons は線が太くそのままだと若干浮き気味なので、色をいじった方が良い。

nikogolinikogoli

外部ファイルデータの取り回し

./static/data.json の中身をコンポーネントで使いたいときの話。

現状では、以下のように扱っている。

  1. dev.ts / main.ts でファイルを読み込み、sessionStorage に入れる
  2. ./routes/_middleware.tshandler でストレージから取り出し、ctx.state に入れる
  3. データを利用したい部分の最上位の islands コンポーネントで context proveider を設定する
  4. データを利用したいコンポーネントで useContextする

ちょっと重い感じがするので改良したい。

nikogolinikogoli

今はもう普通に route 内の個別のページの handler で読み込み render に渡すことにした。
↓のような感じ

[name].tsx
import { Handlers, PageProps } from "$fresh/server.ts"

export const handler: Handlers<StateData> = {
  async GET(req, ctx) {
    const name = req.url.split("/").at(-1)!
    const node_data = await Deno.readTextFile(`./static/denoDoc_${name}.json`)
      .then(tx => JSON.parse(tx))
      .then(list => list[0]) as StateData["node_data"]
    return ctx.render({ name, node_data });
  },
}


export default function MyPage(props: PageProps<StateData>) { ... }

ドキュメントにあるように、/static内のファイルは /ファイル名でアクセスできる。しかし、await fetch("/ファイル名").then(res => res.json())とすると、なぜか invalid URL のエラーになって動かない。

nikogolinikogoli

外側をクリックすると閉じるモーダル系コンポーネント

これのベストプラクティスが知りたい。

nikogolinikogoli

popover API を使ってみた。お手軽。
ただ、backdrop の下の要素を触れてしまうのがちょっと嫌。
https://developer.mozilla.org/ja/docs/Web/API/Popover_API

関連属性に ComponentProps が未対応なので、vscode のエラー消しに↓みたいなのが必要

const ButtonWithPop = (props:ComponentProps<"button">&{
  popovertarget:string,
  popovertargetaction?: "show"|"hide"
}) => <button {...props}/>

背後を暗くしたい場合は、↓のように追加の head の中で::backdropのスタイルを設定する

import { Head } from "$fresh/runtime.ts"

export default function MyPage(props: PageProps<StateData>) {
  return (
    <>
      <Head>
        <style>{`
          ::backdrop { background-color: black; opacity: 0.6;}
        `}</style>
      </Head>
      <Page {...props.data}/>
    </>
  )
}
nikogolinikogoli

.tsx 内部で Element を追加する

現状では

import { render, createElement } from "preact"

const elem = createElement("div", {
  id: "temp",
  style: {position: "fixed", width: "100%", height: "100%", top: "0", left: "0", zIndex: "10"}
})
const countianer = document.createElement("div")
render(elem, countianer)
document.getElementById(some_id)!.appendChild(countianer.firstElementChild!)

直接document.createElement→ append でも良いのだけど、その場合は style 設定が面倒くさい...

nikogolinikogoli

画像ファイルを dataURL で取り回す

import { encode } from "https://deno.land/std/encoding/base64.ts"

const image_URL = await Deno.readFile(IMAGE_PATH)
    .then( data => encode(data) )
    .then( tx => `data:image/${IMAGE_PATH.split(".").at(-1)};base64,${tx}` )

const image = new Image()
image.src = image_URL 
nikogolinikogoli

ファイル選択からの画像ファイルの読み込み

const fileInputElem = document.createElement("input")
const imgElem = document.createElement("img")

const img_file = fileInputElem.files[0]
const reader = new FileReader()
reader.onload = (e) => {
  if (e.target !== null){
    const { result } = e.target
    if (typeof result == "string"){ imgElem.src = result }
  }
}
reader.readAsDataURL(img_file)
nikogolinikogoli

生成したデータをダウンロード

const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'text/plain'})
const link = document.createElement("a")
link.download = "data.json"
link.href = URL.createObjectURL(blob)
link.click()
nikogolinikogoli

相対パスの解決

fresh 関係ないけど

Deno.readTextFile("./xxx.ts")のような Deno系関数における相対パスは、Deno.cwd()を基準にして解決される(と思う)。

対して、dynamic import import("./xxx.ts")における相対パスは、そのスクリプトが記述されたファイルのパスを基準にして解決される。

なので、Deno.cwd()を基準にして dynamic import をしたい場合は、以下のように「絶対パス化+FileURL化」処理を経由する必要がある

import { toFileUrl, resolve } from "https://deno.land/std@0.200.0/path/mod.ts"

async function someFunc(){
  await import(toFileUrl(resolve("./xxx.ts")).href)
}
nikogolinikogoli

以前にもこれに悩んで同じ解決法にたどり着いた痕跡がある。有名な話なのかも?

nikogolinikogoli

Deno から別プロセスを spawn しその出力を継続的に console に出す

const command = new Deno.Command(Deno.execPath(),{
  args: ["run", "-A", "--watch", "main.ts"],
  cwd: "./viewer", //実行ディレクトリの変更が必要な場合はここで設定
  stdout: "piped",
  stderr: "piped"
})

const process = command.spawn()

// preventClose とかのオプション設定は正直よくわからない
process.stdout.pipeTo(Deno.stdout.writable, {preventClose: true})
process.stderr.pipeTo(Deno.stderr.writable, {preventClose: true})

const status = await process.status
if (!status.success){
  throw new Error(`Error in Viewer. Stop processing.`)
}
console.log(`Terminate Viewer.\n`)

「外部処理1 spawn → 終了 → 内部処理 → 外部処理2 spawn → 終了」のように、同一処理の中で複数の異なる Deno.Command を連続的に spawn する場合は preventClose: true がないと処理1が終了した時点で console に pipeできなくなる。

ただ、preventClose: true でも console の readble strem が開けないとかなんとかのエラーが割と出る。間隔が短いとだめなのだろうか?

nikogolinikogoli

クリップボードを読み書きする

await navigator.clipboard.readText()
  .then( async (clipText) => {
    if (clipText.length > 0){
      const tx = funtion(clipText)
      await navigator.clipboard.writeText(tx)
      ok = true
    } else {
      window.alert("クリップボードが空です。")
    }
  })
  .catch( e => {
    window.alert(e)
  })