| @ブログ

75 件ほどあった tech.portalshit.net の記事を取り込んだ。実家に住んでいた 10 年前に始めた技術ブログで、最初は Rails 製の Mephisto 、その次に Jekyll で構築した。まだ GitHub Pages の仕組みが存在する前で、自前で用意したさくら VPS に git push すると自動でビルドして記事が公開されるような仕組みを作ったりしてた。

職業プログラマーになろうとしてもがいてた頃にやってたブログで、いま読み返すと「頑張ってたんだな」感があっていなたい記事が多い。

だいぶ放置していて、いまは S3 で静的サイトとして公開していたのでそのまま放置でもよかったが、 10 年前と違って何でも一カ所にまとめて書いておきたいという気持ちが強くなって取り込むことにした。ブログはトピックを混ぜずに一つのトピックにフォーカスした方がよいと 10 年前は考えていたのだけど、最近の世の中のブログ記事の読まれ方は変わってきていて、一人の人のブログをフィードリーダーに登録して読むというより、 SNS をだら見していて流れてきた記事を適当に消費するというスタイルに変わってきているので、一つのブログに一つのテーマという書き分けは不要になったと感じる。

tech.portalshit.net を取り込んだおかげで Archive ページのグラフに占める技術記事の割合が増えた。

Tech category Bar extension

ちなみに取り込みは以下のようなコードを書いて SQL の INSERT 文に変換した。

require 'yaml'
require 'pathname'

files = Dir.glob(File.join(__dir__, '_posts', '*.markdown'))
files.each do |file|
  content = File.read(file)
  _, header, body = content.split('---')
  header_yml = YAML.load(header)
  title = header_yml['title']
  tags = header_yml['category']
  tags = tags.is_a?(Array) ? tags.map(&:downcase) : [tags&.downcase]
  slug = Pathname.new(file).basename.to_s.sub(/\d{4}\-\d{2}\-\d{2}\-(.+?)\.markdown/, '\1').gsub('_', '-')
  body = body.strip.gsub(/\n/, "\\n").gsub('\'', '\'\'')
  created_at = File.birthtime(file).to_s.sub(' +0900', '')
  updated_at = File.mtime(file).to_s.sub(' +0900', '')
  puts <<~EOS
    INSERT INTO entries(title, user_id, category_id, slug, markup, type, draft, body, frozen_tag_list, created_at, updated_at) VALUES('#{title}', 1, 6, '#{slug}', 'redcarpet', 'Post', 0, '#{body}', '#{tags.join(',')}', '#{created_at}', '#{updated_at}');
  EOS
end

| @技術/プログラミング

Chart

Rechars という React のチャートライブラリを利用して、 Archive ページにカテゴリーごとに記事を集計してグラフ化する機能を作った。

グラフの Bar にカーソルを載せると Tooltip が表示されて、具体的な件数がわかる。

Chart Tooltip

カテゴリごとに表示・非表示を切り替えることも可能。グラフ下のカテゴリー名( Legend )をクリックして切り替えられる。

Chart Show-Hide Toggle

ただし残念なことに Bar を非表示にしたときに Legend の表示を変化させるのが難しくてできていない。

仕事で使ってる Looker とか Redash であれば Legend をクリックして表示・非表示を切り替えることができ、それに連動して Legend の色をトーンダウンさせたりする機能が付属しているが、利用した Recharts にはその機能がなかった。 Bar の表示・非表示切り替えも標準サポートされていなかったので、 GitHub の Issue の情報を頼りに無理矢理実装した。

コードはこんな感じ。結構汚い Hack で、 Bar の表示・非表示を、表示用のキー文字列に空白を追加するかしないかで切り替えている。

  // クリックされたアイテムが `this.state.disabled` 配列の中にすでに存在していれば除外し、
  // 存在してなければ追加する
  selectBar(event) {
    let dataKey = event.dataKey.trim()
    if (this.state.disabled.includes(dataKey)) {
      this.setState({ disabled: this.state.disabled.filter(item => item !== dataKey) })
    } else {
      this.setState({ disabled: this.state.disabled.concat([dataKey]) })
    }
  }

  render() {
    return (
      <ResponsiveContainer height={500}>
        <BarChart
          data={this.state.data}
          margin={{
            top: 20, right: 20, left: 0, bottom: 20,
          }}
          style={{ fontSize: '14px' }}
        >
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="year" />
          <YAxis />
          <Tooltip labelStyle={{ color: '#000', fontWeight: 'bold' }} itemStyle={{ margin: '0 2px 0 4px', padding: '0' }} />
          {// Legend クリック時のコールバックに `this.selectBar` を指定する }
          <Legend onClick={this.selectBar} />
          {/*
            `this.state.categories` 配列と `this.state.disabled` 配列の内容を比較し、
            `this.state.disabled` に追加済のカテゴリーは dataKey に空白を追加することで非表示に
          */}
          {this.state.categories.map((category, index) => {
            let dataKey = this.state.disabled.includes(category) ? category + " " : category
            let color = this.colors[index % this.colors.length]
            return(<Bar key={index} dataKey={dataKey} stackId="a" fill={color} />)
          })}
        </BarChart>
      </ResponsiveContainer>
    );
  }

| @技術/プログラミング

react-select-before-after.jpg

一個前の記事のキャプチャにあるように、従来テキストリンクだった年とカテゴリーの選択をセレクトボックスにした。 git log -S したところ去年( 2019 年)の 11 月頃に変更を行ったみたいだ。もうそろそろ 2020 年になろうとしていて、 2005 年からやっていて年の数が 16 個になろうとしていてさすがに多すぎると思ったので整理のためにセレクトボックス化してコンパクトにした。

利用したのは React Select というパッケージで、色々カスタマイズできるみたいだけど面倒だったので素のまま使ってる。

Archives ページのリファクタリング のときのように、子コンポーネントのイベントをトリガーに親コンポーネントの setState() を呼び出すような作りになっている。作るときは結構難儀したけどおかげでスマートフォンで見たときも年やカテゴリーだけでファーストビューが埋まるということがなくなった。

react-select-smartphone-view.jpg

現在のコードをまるっと貼り付けるとこんな感じ↓。

子コンポーネント(年のセレクトボックス)

import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import Select from 'react-select'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'

import history from './history'

class YearSelect extends Component {
  constructor(props) {
    super(props)
    this.state = {
      data: [],
      selectedOption: null
    }
    this.handleChange = this.handleChange.bind(this)
  }

  static propTypes = {
    match: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired
  }

  async loadYearSelectFromServer() {
    const request = await fetch('/archives/years.json')
    const response = await request.json()
    this.setState({ data: response })
  }

  componentDidMount() {
    this.loadYearSelectFromServer()
  }

  handleChange(selectedOption) {
    const year = selectedOption ? selectedOption.value : null
    if (year) {
      this.props.history.push(`/archives/${year}`)
    } else {
      this.props.history.push("/archives")
    }
    this.setState(
      { selectedOption },
      () => { this.props.update(year) }
    )
  }

  render() {
    const options = this.state.data.map(year => {
      return { value: year, label: year }
    })
    return (
      <div className="year-list">
        <Select
          value={this.state.selectedOption}
          onChange={this.handleChange}
          options={options}
          placeholder="Year"
          isClearable
        />
      </div>
    )
  }
}

const YearList = withRouter(YearSelect)

export default YearList

親コンポーネント( App.js )

import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom'

import YearList from './YearList'
import CategoryList from './CategoryList'
import Archives from './Archives'

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      category: null,
      year: null,
      length: 0
    }
    this.updateCategory = this.updateCategory.bind(this)
    this.updateYear = this.updateYear.bind(this)
    this.setLength = this.setLength.bind(this)
  }

  updateCategory(category) {
    this.setState({ category })
  }

  updateYear(year) {
    this.setState({ year })
  }

  setLength(length) {
    this.setState({ length })
  }

  render() {
    return(
      <Router history={history}>
        <div className="archive-filter">
          <YearList update={this.updateYear} />
          <CategoryList update={this.updateCategory} activeCategory={this.state.category} />
          <div className="entry-length"><p>{this.state.length} entries</p></div>
        </div>
        <Switch>
          <Route
            exact path="/archives"
            render={(props) =>
              <Archives
                category={this.state.category}
                setLength={this.setLength}
                {...props}
              />
            }
          />
          <Route
            path="/archives/:year(\d{4})"
            render={(props) =>
              <Archives
                category={this.state.category}
                setLength={this.setLength}
                year={this.state.year}
                {...props}
              />
            }
          />
        </Switch>
      </Router>
    )
  }
}

export default App

| @旅行/散歩

クヒオビーチ

マイルを貯めるのは低所得の自分には無理だという記事を書いた。

しかしこの記事を書いたあともセブンイレブンで 100 円のコーヒーを買うときもクレジットカードで払ったり、トイレットペーパーを LOHACO で買うときもポイントサイトを経由したり、通りすがりの自販機の釣り銭受けをまさぐって取り忘れの小銭をあさる中学生のようにちまちまマイルを貯め続け、なんとか家族三人そろって特典航空券(エコノミー)でホノルルまで行ける程度のマイルを貯めることができた。金持ちだと毎年ハワイにビジネスクラスで行けるくらいマイルが貯まるみたいだけど、自分の場合は 3 年かかってエコノミー(ローシーズン、 3 人分で 105,000 マイル)が限界だった。

なぜハワイなのか

2013 年に買った POPEYE がハワイ特集号で、「ハワイには若いうちに行こう」と書いてあった。自分はそのとき 32 歳で、コテコテのリゾート地に興味なかったのだけど POPEYE の特集を読んでみると「確かに良いな」と思えたので、なるべく年を取る前にハワイに行っておきたいなと思っていた。

ここ一年半くらい、身を粉にしながら働くものの大した成果を残すことができず、完全に人間として腐り始めていた。このままでは玄界灘で魚の餌になるくらいしか社会貢献の方法がないと思ったので、休みを取って旅行に行ってみることにした。

旅の準備

福岡から成田へ移動

特典航空券界の常識をわかってなくて、マイルが貯まったらすぐに特典航空券を申し込めるのかと思っていたけどそういうわけではなさそうだった。マイルが貯まったので「来月旅行にでも行くか」とほくほくしながら特典航空券を申し込もうと ANA のサイトを覗いたら全然席が空いてない。ハワイのような人気路線の特典航空券は申し込めるようになった瞬間全部埋まってしまうみたいだった。 ANA の国際線の特典航空券の予約は 355 日前から可能になり、マイレージクラブのランクが高い人(= @t32k さんなどの金持ち)ほど申込みが優遇されるシステムなので、ランクの低いマイレージクラブ会員(=貧民)が特典航空券を申し込もうとしたらほぼ一年後の日付にするしかない。手持ちの一番古いマイルの有効期限が 2019 年の 2 月だったので、この時期に本当に旅行に行けるかどうかもわからないまま 2019 年の 2 月末に 2020 年 2 月のチケットを取った。申し込んだとき、どこに行くかとか何をするかとかどこに泊まるかとか何も考えられていなかった。やっと旅の予定を決め始めたのは 2020 年の正月だった。その後コロナウイルスの話が出てきて一旦は旅行を取りやめようかとも思ったが、先延ばしにすると逆に騒動が広がりそうだと思って予定通り 2 月中旬に出発した。

成田で暇つぶし

ハワイに行ってどうだったか

よかった。

カジュアル

ヨーロッパを旅行するときは結構緊張したが(人種差別とかマナーに気をつけたりで気をつかう)、ハワイは気楽でとてもよかった。

アメリカ人に対する良くないイメージが払拭できたのもよかった。少なくともハワイに住んでたりハワイに旅行に来てるアメリカ人からは、ヨーロッパの安宿で見かけるような声がでかくて何でもアメリカ流を押し通そうとする感じの悪さがなくて、嫌な気持ちになることなく旅行できた。

快適な移動

レンタカーや Uber 、 Lyft で移動したので移動で荷物を抱えて大変な目に遭ったり、タクシーにぼったくられないか心配したりしなくて済んだのが良かった。海外で車を運転するのは初めてだったが、ハワイの道は広くてとても運転しやすかった。

スーパークレジットカード社会

何でもカードで払えるので両替する必要がない。現金はチップを払うときか空港の自販機で飲み物を買うときくらいしか使う機会がない。 ABC ストア(ワイキキに 50m 間隔である旅行者向けのコンビニ)で現金で金払ってるのは日本人旅行者くらいだった。

eSIM 便利

今回は自分の iPhone 11 も嫁さんの iPhone XR も eSIM に対応していたので、香港の 3 というキャリアの eSIM を利用してハワイでも日本にいるとき同様に携帯が使えたのが良かった。 5 年前にギリシャに行ったときはちまちま Pocket WiFi をオンにしてインターネットにつないでいたので不自由極まりなかった。

ホテルの取り方

宿は Hotels.com と HIS で取った。 Hotels.com が最安かというとそういうわけではなく、マリオットなど結構良いホテルのチェーンだと HIS の方が料金安い&朝食付きだったりしてお得だった。

旅程

旅程 宿泊
Day 1 家 🚕 福岡 🛩 成田 ✈️ ホノルル 🛩 ヒロ Hilo Hawaiian Hotel
Day 2 ヒロ 🚙 アカカの滝 🚙 MKVIS Hilo Hawaiian Hotel
Day 3 ヒロ 🚙 ワイピオ渓谷 🚙 コナ King Kamehameha's Kona Beach Hotel
Day 4 コナ 🛩 ホノルル Queen Kapiolani Hotel
Day 5 ホノルル 🌴 Queen Kapiolani Hotel
Day 6 ホノルル 🚙 ノースショア Queen Kapiolani Hotel
Day 7 ホノルル ✈️ 成田 🛩 福岡

ハワイ島とオアフ島に三泊ずつした。福岡空港から成田へ行き、成田で 5 時間時間を潰してホノルルへ。ホノルルからハワイアン航空に乗り換えてハワイ島のヒロへ。ヒロに二泊してコナへ移動し、コナで一泊してからコナ空港からまたハワイアン航空でホノルルへ。なおコナではホテルにパスポートを忘れて大騒ぎとなったが、ハワイアン航空の職員の人がめっちゃ親切でスーパー助かった。ギリシャでエーゲ航空に散々な目に合わせられたのとは大違いだった。ホノルルに三泊して成田経由で福岡に戻った。 6 泊 8 日の旅(一泊は飛行機の中)。

ハワイ島

ホテルの部屋から見るマウナケア

ハワイ島ではアカカの滝、マウナケア(中腹まで)、ワイピオ渓谷、コナブリュワリーの工場レストランなどを訪れた。主にレンタカーでドライブしていて、ハワイ島の北半分をぐるっと回った。

アカカの滝

マウナケア

ワイピオ渓谷

ハワイ島はハワイ諸島の中では一番新しい島でまだ火山の荒々しい感じが残ってる。マウナケアは 4200m もある高い山だが、裾野が広がっていて草原が広がり、景色が生まれ故郷の阿蘇に似ていて海外旅行に来てるのに帰省してるみたいな感じで不思議な感覚だった。

ハワイ島の形式

オアフ島

オアフ島ではワイキキに滞在しつつ、ビーチで遊んだり、街をうろついたりした。

ワイキキビーチ

オアフ島でも一日は車で遠出して、ノースショアまで行ってパタゴニアで環境に配慮しながら家族で爆買いしたりした。

ノースショアハレイワ

patagonia で爆買い

ダイアモンドヘッドが見える宿に泊まっていて、できれば登りに行ってみたいと思っていたけどうだうだ無為に時間を過ごしてしまってついぞ登ることができなかった。残念。

ホテルの部屋から見るダイアモンドヘッド

ワイキキは福岡でいうと西通りといった趣でひたすらショッピングするのが好きな人にはよさそうだが少々退屈だった。ライドシェアの Lyft で、カカアコという昔は治安が悪かったけど最近は小洒落た店が増えているエリアに行ってオシャンティなレストランでラム肉を食べたりした。

ラムチョップ

POPEYE で紹介されていた Ray's Cafe という店が安くて巨大なリブアイやロブスターが食べられるようだったが、治安の悪いエリアにある地元民向けのレストランのようで閉店時間が早く、今回は行くことができなかった。

旅行中のツイート


正直、ハワイは一回行けば充分だろうと思っていたが、旅行から帰ってきてからは「またハワイに行きたい」という気持ちが日々募っていく一方だ。酒のやまやに行ってコナビールやフラ印のポテトチップスを買ったり、中公新書のハワイの歴史についての本を読んだり、Amazon Prime ビデオで HAWAII FIVE-0 というドラマを見たりしている。

HAWAII FIVE-0 ではよく主人公が山登りに行っている。ハワイといえば海というイメージあるけど、ダイヤモンドヘッド以外でも登り甲斐のありそうな山がたくさんある。今度は山に登ったり、地元民向けの治安が悪いエリアの店にも乱入してみたりしたい。

ワイキキの夕景

それにつけても金の欲しさよ。

| @Mac/iPhone

Mac や iOS 用カレンダーアプリの Fantastical が買い切り型のビジネスモデルからサブスクリプションに移行した。

Fantastical 2 を買っていたユーザーは既存機能は無料で使えるみたいだけど、 iPhone 上でカレンダーセットを複数使ったり、複数のカレンダーに存在する別のイベントを統合して表示する機能はサブスクリプションに登録しないと使えない。

サブスクリプション、よさげなビジネスモデルに見えるが、何でもサブスクリプションにすればよいというものではないと思う。サブスクリプションにすることで、ビジネス側にもユーザー側にもメリットがなければ意味がない。 Netflix や Amazon Prime 、 Adobe CC に関しては個別に買ったりレンタルしてたら膨大な金額がかかるものがサブスクリプションになることで月々の負担を抑えながら利用でき、その結果ユーザー数が増えることでビジネスサイド、ユーザーサイドの両方にメリットがある。一方で、もともと 5000 円程度の買い切りソフトウェアが月額 600 円のサブスクリプションに移行しても既存ユーザーにとっては値上げでしかなく、 5000 円の買い切り価格に逡巡していたユーザーが加入する後押しにもならないのでパイが拡大しない。誰にとってもメリットがない。こういうのは本当に無駄なのでやめてほしい。

| @散財

iPhone 11 表面

親父にあげていた iPhone 6 のバッテリーが寿命のようで、 iPhone 7 を譲るために新しい iPhone を買った。 iPhone 11 の 128GB にした。 88000 円くらい。魅惑のオリコローン、 24 回払い分割金利手数料無料で購入した。 iPhone 7 は 2016 年末に買ったのでまるまる 3 年使った。

外観と Face ID

ごっついレンズが複数付いた外観は好きになれなかった。 iPhone 11 Pro は高すぎて買えなかったが外観的にも個人的に受け入れられなかった。 iPhone 11 のレンズ二つの外観もいびつに見えたが、いまさら iPhone XR を買うのは何だかなという感じなので消極的に iPhone 11 を選んだ。

iPhone 11 背面

ケースを付けるかは迷った( iPhone 7 はケースを付けない状態が一番使いやすかった)が、結局付けた。ケースを付けないとレンズの出っ張りが邪魔で、背面を下にしてテーブルに置いたときに安定しない(ガタガタする)。レンズ面の出っ張り対策としてケースは必要だと思った。ケースは Amazon で評価のよかった以下を騙されたと思って買ったけど実際とても良かった(サクラレビューではないようだった)。

ホームボタンと Touch ID のないモデルは初めてで、 Face ID は Apple Pay の支払時に不便かなと懸念していたが、スリープボタンをダブルタップして読み取り機にかざす前に Face ID 認証すればよいのでかえって便利になった。 NFC 認証のスピードも速くなっている気がする(コンビニのレジでもたつくことがなくなった)。

Face ID になってよかったことに、スクリーンプロテクターが全面を覆い隠せるようになったことがある。 Touch ID 方式だとホームボタンは指紋認証のために覆うことができず、スクリーンプロテクターに妙な穴が開いてたりしていて不格好だった。全面を覆えるものを買ったがとても良い。何もはっつけていないみたいに見える。

スクリーンプロテクターは以下のものを買った。貼り付け時に貼り付け位置を失敗しないようにサポートするプラスチックの枠が付いていてスーパー便利だった。これまで位置ずれを気にしながら貼っていたのがバカみたいだった。

重い

iPhone 11 、とにかく重い。調べたところ 194g のようだった1。 iPhone 7 は138g だったみたいなので 56g くらいしか重くなってないのに随分重く感じる。検証機として会社から借りてる Pixel 3a に比べても重い。 50g の差がスマートフォンでは決定的な違いになるんだなと思った。

iPhone 11 と Pixel 3a

電池のもち

重くなったおかげか、バッテリー容量が増えていて電池がとてもよくもつようになった。 iPhone 7 はバッテリーを一度 Apple Store で交換してもらったが、それでもヘビーに使うと一日電池がもたなかった。 iPhone 11 で不意に電池切れになって困った、ということは起こりそうにない。

写真

写真に関しては、日中に撮れる写真の画質は iPhone 7 の頃と大差ないと感じる。もっと綺麗な写真が撮れるかなと思ったけどそうでもなかった。ポートレートモードも試したが、ぼかし処理が甘くて(特に物撮り)被写界深度的に「そこ違うんだけどな」というところがぼけてしまったりする。一眼で撮るのには及ばない。

広角レンズはおもしろい。室内や街並みなどは広角で撮ると楽しい。

街並み

川や道路の大きさ、スケール感が伝わる。

博多川 リバレイン通り

室内

店内の壁一面に手書きのいろんなメニューが貼ってある面白定食屋の様子。店内は広くなく、標準のカメラでは到底壁一面の様子などを写すことはできないが、広角レンズで撮影することができた。

定食屋店内 定食屋店内

ナイトモード

ナイトモードもなかなかおもしろい。 NIKON Z6 を買って最近のカメラの夜景撮影能力に感動していたが、 iPhone 11 のカメラで撮る夜景も大分すごい。以下は近所の山に夜景を撮りに行ったやつの比較。

街の夜景

Z6 で撮った写真は三脚に固定して撮っている。一方 iPhone は手持ち。これはすごい。

今山から見る今宿( NIKON Z6 で撮影)

今山から見る今宿( iPhone 11 で撮影)

夜の踏切

Z6 は ISO 感度が 51200 まで上がってしまってノイズだらけになってしまっている。 iPhone 11 の方は ISO 800 で撮って補正しているのでノイズが少ない。

今宿の踏切( NIKON Z6 で撮影)

今宿の踏切( iPhone 11 で撮影)

こんな感じで、ミラーレスカメラと同等の、状況次第ではミラーレス以上に綺麗な夜景写真が撮れてしまう。画像補正処理技術の向上すごい。


最初は「う〜ん」と思っていた iPhone 11 の外観や重さだけど、使っていくうちに広角レンズの便利さや電池のもちの良さなど、 iPhone 11 の良さがしみじみとわかってきた。値段は iPhone 11 Pro ほどには高くないし、買ってよかったなと思える端末でした。便利。

| @写真

箱石峠から眺める阿蘇高岳

2018 年末に NIKON Z6 を買った。その前は NIKON D90 を 10 年近く使ってた。 NIKON D90 で撮れる写真にあまり不満はなかったが、フルサイズへの憧れと動画性能のしょぼさ(静止画は D90 の方がよいものが撮れるが、動画は明らかに iPhone の方がよいものが撮れた)に困っていて買い換えを決意した。人生で初めて新発売のカメラを発売前から予約して購入した。 48 回分割払い。

NIKON Z6 の良さは色々ある。通し F4 で撮れる 24-70m レンズ(レンズキットを買った)、 ISO 感度の高さ、チルト液晶、奥行きにも対応したデジタル水準器、 SnapBridge 対応(スマートフォンの GPS を利用した位置情報埋め込み、撮った写真をすぐに Bluetooth でスマートフォンに送れる)などなど。スマートフォン時代に対応するための正統進化という趣がある。 NIKON は真っ当ないい製品を作ったなと思う。

というわけでまずは NIKON Z6 で撮った写真からご覧下さい。そのあとに iPhone 7 で撮った写真で今年を振り返ります。

Continue reading...