| @Mac/iPhone

Touch Bar

リモートワーク中心の世の中なので Slack の Status で離席していることやミーティング中であることが分かると便利なはず。というわけで自分はなるべく Slack の Status を更新するようにしているが、 Slack アプリ内での Status の更新は面倒くさい。メニューを押して絵文字選んでひと言アップデートを入力とか毎度やってられない。ボタン一発で Status を更新したい。

MacBook Pro の Touch Bar は評判が悪い。自分もあまり便利だと思わないのだけど、一つだけ便利な使い方があって、それがこの Slack の Status アップデートボタンを配置するというもの。 Touch Bar に配置されたボタンを押すだけで食事中であることや退勤済であることを Slack の Status として表示できるようになる。めっちゃ便利。

なお、オリジナルのアイディアとソースコードは 9m さんのものです。

必要なもの

準備

1. Slack の API Token を発行する

2. 9m さんの gist を clone し、手元で動かせるようにする

$ ghq clone https://gist.github.com/af5894ced5cc1ac38bfd2687cad7c780.git slack_status
$ cd clack_status
$ bundle install
$ echo "SLACK_TOKEN=XXXX" > .env
$ bundle exec app.rb "🍺" "退勤しました"

ちゃんと設定できてれば以下のようになる。

コマンドラインから Slack Status をアップデートしている様子

3. Automator を開き、クイックアクションを設定

新規作成で「クイックアクション」を選ぶ。

Automator を開き「クイックアクション」を新規作成

アクションの中からシェルスクリプトを選ぶ。

シェルスクリプトを選ぶ

実行したい処理をシェルスクリプトで書く。

実行したい処理をシェルスクリプトとして記載

自分は以下のようにしている。

export PATH="~/.rbenv/shims:$PATH"
export LC_ALL=ja_JP.UTF-8
export LANG=ja_JP.UTF-8
cd /Users/morygonzalez/src/gist.github.com/slack_status
bundle exec ruby app.rb "🚽" "放尿 or 脱糞中です"

なお、赤枠で囲った「ワークフローが受け取る項目」は「入力なし」にしておかないとちゃんと動かないので注意。

入力なしを選択

設定完了したら名前を付けて保存する。自分の場合は Slack トイレ などのような名前にしている。この作業を追加したいコマンドの数だけ繰り返す。

4. キーボードショートカットの割り当て

システム環境設定 -> キーボード -> ショートカット -> サービス の順に進む。正しく Automator でアクションを設定できていれば「サービス」の一覧に表示されるので、割り当てたいショートカットキーを割り当てる。

ショートカットの設定

5. BetterTouchTool で Touch Bar をカスタマイズする

タッチバーに表示されるボタンのアイコンとラベル文字を選び、タップしたときにショートカットキーが実行されるようにする。

BetterTouchTool で Touch Bar をカスタマイズ

こうすることで Touch Bar から Automator のクイックアクションが実行され、めでたく Slack の Status がアップデートされるようになる。

ちなみに自分の Touch Bar はこんな感じ。

Touch Bar の様子

ほこりをかぶってる Touch Bar を是非有効活用してあげてください。

Touch Bar がないパソコンを使っている人向けの情報

Touch Bar のない Mac を使っている人はこのやり方を使えないので Slack の Google Calendar 連携機能を使うと良いと思う。設定に Status Sync という項目があるのでこいつを On にすると、 Google Calendar で予定が入っている時間になると Slack の Status を自動で更新してくれる。

Google Calender の Status Sync

会議中であることくらいしか共有できないので Touch Bar にいろんなボタンを配置するのに比べたら不便だけど、カレンダーに予定を入れておくだけで Slack の Status を更新できるようになるのは便利。

今後の課題

良くありがちなのが「仕事中」の Status のまま退勤してしまうというやつ。夜中や週末も仕事している異常な人になってしまう。スマートフォンからも同様にめっちゃ手軽に Slack の Status をアップデートしたいけどまだソリューションを見つけられていない。情報お持ちの方いたら教えてください。

| @登山/ランニング

山頂でコーラ

8 月の最後の日曜日に脊振山から金山まで縦走した。 2 年前の糸島四座縦走(未遂)以来の長い日帰りソロハイクだった。歩いた距離は平坦地の距離も合わせて 21km 。くたくたに疲れて思わず山行中にコーラを 3 本飲んでしまった。

いつかは脊振山系全山縦走(福岡県と佐賀県を隔てる県境の山系で西端の十坊山から東端の基山まで 70km ある)をやってみたいと思っていたけど、今回脊振山・金山を縦走してみて自分は到底そのレベルに到達することはできないなと思った。脊振山から金山へ縦走していて、途中のところでくたくたに疲れて地面に座り込んで休憩してしまった。山ではがんがん登山するよりも、ほどよく休んでコーヒーいれて飲んだり、冷凍焼売を蒸して食べたりだとか、ピクニック + ハイキング的なものの方が自分の性分に合っている。

鬼ヶ鼻岩で焼売

帰りの交通機関の時間を気にしながらの登山は目的が達成できたときはうれしいが、ゆっくり写真を撮ったり食事したりすることができず少しせわしない。かといって車を使った登山をするのは負けた感じがするし、車を使わない縦走ならではの下山後の酒というご褒美がなくなってしまう。となるとある程度歩く登山の場合は日帰りではなく山に泊まることを考慮すべきなのかもしれない。

脊振山へのルートは沢が多くもみじが美しかった。新緑の季節や秋に訪れると最高だろう。

もみじの美しい谷

金山も山頂は眺望がイマイチと聞いていたが、脊振山・金山間の縦走路は稜線歩きの快適な道が多く、脊振山まで車で来て鬼ヶ鼻岩くらいまで歩いて引き返す、というような感じだと家族連れでも楽しめそう。

九州自然歩道

白砂の眺望の良い場所

鬼ヶ鼻岩からの景色

猟師岩山・金山間は歩く人が少ないのか笹藪が茂っているし、結構痩せ尾根があって万が一転倒すると簡単に落命しそう。

金山までの縦走路

予定では金山から花乱の滝コースで下山する予定だったが時間が押してしまい、バスの時間に余裕がある坊主ヶ滝ルートで下山することにした。

当初の予定ルート(花乱の滝方面から下山)

実際のルート(坊主ヶ滝方面から下山)

ただし坊主ヶ滝ルートは罠があって、登山口までの時間は確かに短いが、バス停まで 40 分程度舗装路を歩く必要があって結局バスに間に合わず、本数の多いバス停までさらに 1km 程度歩いた。疲れた体にはこたえた。

日暮れ後の田んぼ

バスで西新まで移動して、一人しめやかに祝杯を挙げた。ラーメン一杯 290 円の店で 1300 円くらいお金使った。

西新のラーメン屋で

| @雑談

長垂海浜公園近くのコインパーキング

長垂海浜公園には無料の駐車場はありません。有料のコインパーキングを利用する必要があります。料金、駐車可能台数、停めやすさをまとめています。

はじめに

長垂海浜公園の松林

福岡市西区の今宿にながたれ海浜公園という砂浜や遊具のある広い公園があります。波が穏やかで海を眺めてぼーっとするには良い公園なのですが(魚釣りをしている人もいます)、駐車場がありません。従って近所に住んでる人だけが訪れるスポットになっています。しかし最近、公園近くにいくつかコインパーキングができ、車でもアクセスしやすくなりました。公園と海に近いコインパーキングを五つほど紹介したいと思います。

なお JR 筑肥線の今宿駅前に広めのコインパーキングがいくつかある*1ので、運転に自信のない方やこれから紹介する駐車場が満車の場合はそちらを利用しても良いと思います。今宿駅から長垂海浜公園までは歩いて 10 分程度です。

コインパーキング一覧

名前 最大料金 駐車可能台数 停めやすさ
ライオンパーク今宿 600 円 5 台
フラットパーク今宿 600 円 7 台(軽・小型専用)
三井のリパーク 今宿駅前一丁目第2 500 円 5 台
今宿駅前1丁目パーキング 500 円 3 台
國﨑真クリニック提携駐車場 300 円(曜日時間限定) 25 台
ワイズパーキング今宿駅前1丁目 500 円 3 台(一台は軽専用)

ライオンパーク今宿

今宿ライオンパーキング今宿ライオンパーキング

唐津街道沿い。 5 台駐車可能です。街道沿いなので車の出し入れはしづらいと思います。右折入庫は難易度高いです。頭から突っ込んで出庫のときにニッチもサッチも行かなくなる可能性あり。必ずお尻から入庫したいものです。

駐車料金

時間帯 料金 最大料金
7:00 ~ 19:00 100 円 / 60 分 600 円
19:00 ~ 7:00 100 円 / 60 分 300 円

駐車可能台数

5 台

出し入れのしやすさ

フラットパーク今宿

フラットパーク今宿フラットパーク今宿

唐津街道沿い。一つ目の今宿ライオンパーキングのすぐ近く。 7 台駐車可能ですが小型か軽専用となっています。横幅が狭いので大きめの車で入庫してしまうと出し入れに難儀しそうです。

駐車料金

時間帯 料金 最大料金
8:00 ~ 20:00 100 円 / 60 分 600 円
20:00 ~ 8:00 100 円 / 60 分 300 円

駐車可能台数

7 台(軽・小型専用)

出し入れのしやすさ

三井のリパーク 今宿駅前一丁目第2

三井のリパーク 今宿駅前一丁目第2三井のリパーク 今宿駅前一丁目第2

唐津街道から少し入った住宅街の中にあります。広めの月極駐車場のうち、手前のオレンジ色の線の部分がコインパーキングで、 5 台止められます。時間あたりの駐車料金は 300 円と天神と同レベル。入庫したら最大料金を支払うつもりで利用するのが良いでしょう。前の道の車通りが少なく、駐車場も広いため出し入れはしやすいと思います。

駐車料金

時間帯 料金 最大料金
0:00 ~ 24:00 300 円 / 60 分 500 円

駐車可能台数

5 台

出し入れのしやすさ

今宿駅前1丁目パーキング

今宿駅前1丁目パーキング今宿駅前1丁目パーキング

三井のリパーク 今宿駅前一丁目第2すぐ近くのコインパーキングです。民家の前庭部分がコインパーキングにしてあり、 3 台駐車可能です。時間あたりの料金はやはり高めです。こちらは紹介するコインパーキングの中で最も出し入れしやすいと思います。運転に自信のない方におすすめ。

駐車料金

時間帯 料金 最大料金
0:00 ~ 24:00 300 円 / 60 分 500 円

駐車可能台数

3 台

出し入れのしやすさ

國﨑真クリニック提携駐車場

國﨑真クリニック提携駐車場國﨑真クリニック提携駐車場

医院併設の駐車場。日・祝日は最大料金の設定がありますが、平日と土曜日の昼間は最大料金の設定がないためご注意ください。100円/20分で非常に高額になる可能性があります。

駐車料金

時間帯 料金 最大料金
7:00 ~ 20:00 (月〜土) 100 円 / 20 分 なし
20:00 ~ 7:00 (月〜土) 100 円 / 60 分 300 円
7:00 ~ 20:00 (日・祝日) 100 円 / 60 分 300 円
20:00 ~ 7:00 (日・祝日) 100 円 / 60 分 200 円

駐車可能台数

25 台

出し入れのしやすさ

ワイズパーキング今宿駅前1丁目

マリブ今宿シーサイドテラスマリブ今宿シーサイドテラス

海辺のシェアオフィスSALTが入居するマリブ今宿シーサイドテラスの駐車場です。こちらも終日 1 時間あたり 300 円で、 500 円の最大料金が設定されています。 3 台駐車可能ですが一台は軽専用です。ちょっと駐車スペースが狭いかな? 人気のパン屋、ヒッポー製パン所に最も近いです。

駐車料金

時間帯 料金 最大料金
0:00 ~ 24:00 300 円 / 60 分 500 円

駐車可能台数

3 台(うち一台は軽専用)

出し入れのしやすさ


以上、長垂海浜公園近くのコインパーキング情報でした。自分自身、今宿で住む場所を探していたときに車を停めて街をゆっくり見て回りたいと思っても車を止められる場所がなく難儀しました。長垂海浜公園や長垂海岸に遊びに行きたいという方のほか、今宿に引っ越しを検討されている方も街探索の際にお役立てください。長垂海岸では以下のような夕日が眺められます。是非海の近くに車を停めて夕日を眺めに来てください。

長垂海岸の夕暮れ

*1: 2021-08-01 更新: 駅前のコインパーキングが一つ減りました。マンション建設中です。また駅前のコインパーキングは以前は 60 分 100 円程度でしたが、現在は 60 分 200 円〜に値上がりしてきていますのでご注意ください。

| @ブログ

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

| @料理/食事

DSC_3658.jpg

2018 年から 2019 年にかけてよく挽肉料理を作って食べた。最初はキーマカレーから入って餃子になり、最終的にはラザニアに落ち着いた。ラザニアを作るようになってからはラザニアしか作ってなかったが、餃子アドベントカレンダーというものを発見して久々に餃子を作ってみたくなったので餃子を焼いた。低所得ゆえ餃子の名店食べ歩記などではありません。一般人の普通の餃子に関する考察です。

Continue reading...