縦書き文庫の各種機能を改善しました。
作品一覧にてタグが表示されるように
作品の一覧にて、タグが表示されるようになりました。
これによって、同じ系統の作品にアクセスしやすくなりました。
シリーズに追加できる作品数を20から50に増やしました
大菩薩峠(41作品)のように大きなシリーズもあるので、シリーズあたりの作品数を20から50に拡張しました。
縦書き文庫の各種機能を改善しました。
作品の一覧にて、タグが表示されるようになりました。
これによって、同じ系統の作品にアクセスしやすくなりました。
大菩薩峠(41作品)のように大きなシリーズもあるので、シリーズあたりの作品数を20から50に拡張しました。
作ったきっかけは、最近買ったウイイレ2020の操作が、ちっとも頭に入らなかったからです。。。
一応ゲーム内にマニュアルがあるんですけど、文字とか矢印で説明されても、ピンと来ないんですよね。。。
そこで思いついたのが「ゲームの操作をぱぱっと記述したら、パッド上の動きをアニメーションを再生しつつ、操作ログも出してくれる」みたいなものがあったら、理解が捗るかなあ〜?ということです。
というわけで作ったのが、combo-scriptと、combo-playerです。
例えばcombo-scriptで昇龍拳を記述して、combo-playerで再生するとこんな感じになります。
その他、色々なコマンドについては、デモサイトを見てください。
上のデモサイトでは、ウイイレの他に、スト5とか鉄拳のコマンドとかも追加しておきました。
メニューの「Games」から選択できます。
コマンド部分は編集可能になっているので、色々と書き換えて遊んでみてください。
combo-script
は、ゲーム操作を記述するための言語です。こんな風に記述します。
up, up, down, down, left, right, left, right, A, B
right, down, (right, down), punch
(right, down)
は右と左を同時押し(つまり右下)という意味です。
一般にボタンの同時押しは(X, Y, Z...)
のように記述します。
setL(0), moveL(0, 90), rotateL(90, 45), punch
これは左スティックを0度(真右)に方向け、そこから真下に動かし、更にそこから45度(右斜め下)の位置まで回転させ、最後にパンチボタンを押す、という意味です。
setL
, moveL
, rotateL
の引数は、傾ける先の角度ですが、この角度は時計回りであることに注意してください。時計回りなので、90度は真上ではなく、真下です。
R2 { square }
上の構文はHolding Syntax
と言って、X { Y, Z, ... }
などと書くと「X
の操作をしながらY
,Z
」という意味になります。
なので上のコマンドは
という意味になります。
詳しくは公式サイトを見てほしいのですが、大雑把に説明すると、
punch
と書いたら、ユーザー定義のpunch
ボタンがあって、それを押して離す、という一連の動作として解釈されます。つまり、punch
という名前のボタンをどう解釈するかは、combo-scriptを組み込む実装者に任されます。foo(1,2)
と書いた場合、そういう定義済みの関数はないので、ユーザー定義のプラグイン関数とみなされます。そして、実際の動作はcombo-scriptを組み込む実装者に任されます。button1
はいいけど、1button
は駄目。up
と書いてもUP
と書いても同じです。これは関数も同じ。強パンチ
とか書いても問題ありません。コンパイルの仕方とか、それをどうやって自分のプレイヤーアプリに組み込むか、みたいなことは公式サイトを確認してください。
作品に登場人物を登録した場合、先頭ページに「登場人物の一覧」が表示されるようになりました。
市販の書籍でも、だいたいこんな感じで冒頭に表示されてますよね。
登場人物の挿絵については、小説本文の下側に「登場人物」のタブメニューがあって、そこをクリックすると挿絵付きで一覧が表示されます。
人物をクリックすると、こんな感じで詳細情報が表示されます。
jingoo 1.4.0をリリースしました。
Jg_template
モジュールを部分的に改良したJg_template2
というモジュールが追加されました。これまでJg_template
モジュールは、次のようにmodels
に連想リストを与えていました。
open Jingoo open Jg_types let result = Jg_template.from_string "{{ msg }}" ~models:[ ("msg", Tstr "hello, world!"); ]
Jg_template2
モジュールからは、models
に名前を与えたら値を返す関数を渡します。
open Jingoo open Jg_types let alist = [("msg", Tstr "hello, world!")] let result = Jg_template2.from_string "{{ msg }}" ~models:(fun key -> try List.assoc key alist with Not_found -> Tnull )
これの何が良いのかというと、string -> tvalue
という型の関数にさえなっていれば、データソースは何でも良くなることです。
例えばデータソースを(連想配列より高速な)Hashtbl
にすることもできます。
open Jingoo open Jg_types let htbl = Hashtbl.create 1 in let () = Hashtbl.add htbl "msg" (Tstr "hello, world!") in let result = Jg_template2.from_string "{{ msg }}" ~models:(fun key -> try Hashtbl.find htbl key with Not_found -> Tnull )
データソースなしで、単なる関数にしてしまうと、さらに高速です。
open Jingoo open Jg_types let result = Jg_template2.from_string "{{ msg }}" ~models:(function | "msg" -> Tstr "hello, world!" | _ -> Tnull )
関数にすることで、乱数値みたいなものを扱うこともできます。
open Jingoo open Jg_types let result = Jg_template2.from_string "{{ msg }}, {{ rand }}" ~models:(function | "msg" -> Tstr "hello, world!" | "rand" -> Tint (Random.int 100) (* 0 ~ 100 の値をランダムに返す *) | _ -> Tnull )
将来的にはこっちをメインのAPIとして使用したいのですが、旧版のAPIで既に広く使用されてしまっているので、今回はモジュールを別名で分けることで対応しました。
Togetterの次の記事に「探す作業が嫌だ」みたいなことが書かれていて「そりゃごもっとも…」と思ったので、表題の件を実装しました。
少年ジャンプ+副編集長が大学1年生から「漫画アプリのUIについて物申したい!」というDMが来て実際に会って考えが整理されて意義深い時間になったという話 - Togetter
これまでは、シリーズを設定している作品について、続きを読む際に、いったんシリーズ作品のリンクへ飛んでから、自分で続きを探さないといけませんでした。
これからは続きがある場合は、本文の最後にリンクが表示されます。
当たり前にそうあるべきことが、これまで出来ていなかったことが、ちょっと恥ずかしいです。
nehan6からnehan7にバージョンアップしました。
npm install --save nehan
nehan.css
が不要になりました。vertical-align: middle
が効くようになりました。PageReader
は廃止されました。今後はPagedHtmlDocument
を使用してください(後述)。縦書き文庫のビューアーも、新バージョンのエンジンに差し替えました。
nehanのコードサイズが減ったぶん、ページの読み込みも(少しだけ)速くなったと思います。
500KByteほど削減されているので、モバイル環境なんかでは、そこそこ効果が大きいのではないでしょうか。
UIについても、ちょっとだけ刷新しました。
これまでは「本文」と「目次・登場人物」の2列で表示していたのですが、今後は本文を一列(ワンカラム)で表示するようにして、「目次・登場人物」は本文下のメニューに移動させました。
これにより、本文は表示領域が広くなり見やすくなった一方、目次や登場人物に対する操作性は悪くなってしまったので、これに関しては今後の課題としておきます。
(本文の横に目次やら人物やらのタブを表示したらいいのかなあ、と考えています)
だいたいこんな感じで使います。
import { PagedHtmlDocument, CssStyleSheet } from 'nehan'; const src = "<h1>hello, nehan!</h1>"; const doc = new PagedHtmlDocument(src, { styleSheets: [ new CssStyleSheet({ "body": { "writing-mode": "horizontal-tb", // or "vertical-rl" // インライン方向のサイズ(横書きならwidth、縦書きならheightに相当) "measure": "450px", // ブロック方向のサイズ(横書きならheight、縦書きならwidthに相当) "extent": "500px", "font-size": "16px", "padding": "1em" } }) ] }); const $dst = document.querySelector("#dst"); // 結果を格納するDOM // 描画を開始(非同期処理) doc.render({ onPage: (ctx){ const page = ctx.caller.getPage(ctx.page.index); // ページを評価 $dst.appendChild(page.dom); // 評価したページのDOMを注入 $dst.appendChild(document.createElement("hr")); // 一応、分割線 }, onComplete: (ctx){ console.log(`終わりました(${ctx.time}msec)`); } });
nehan
は論理組版エンジンなので、領域サイズの指定にwidth
とかheight
などは使えません。
代わりにmeasure
とかextent
という論理プロパティで指定します。
measure
は横書き(writing-mode:"horizontal-tb")ではwidth
のことです。
縦書き(writing-mode:"vertical-rl")ならheight
になります。
extent
は横書き(writing-mode:"horizontal-tb")ではheight
のことです。
縦書き(writing-mode:"vertical-rl")ならwidth
になります。
同じく、top
, right
, bottom
, left
といった名前は、nehanではそれぞれbefore
, end
, after
, start
と指定します。
例えば横書きのmargin-left
は、nehanではmargin-start
です。
before/afterはブロック方向の前後で、start/endはインライン方向の前後、と覚えてください。
上のサンプルでは、appendChild
を使って、組版結果をどんどん追記する「段組み方式」で表示していますが、ページ送りで表示したいときは、自前で現在表示するページやページ番号などを管理する必要があります。
その場合、起動時には先頭ページだけを表示し、それ以降のページはページ送りをしたタイミングでdoc.getPage
を使って動的に対象ページを取得し、画面上のDOMを差し替える、という実装になるでしょう。
ページ送りの場合の実装については、サンプルのbook.tsなどが参考になるかもしれません。
結論だけ先に述べておきます。
以下に理由を書きますが、非常にしょうもないことです。
nehan(<=6.0.38)においては、package.jsonのtypes
という属性の値が./dist/indx.d.ts
となっていました。
しかし実はこれ正しくは./dist/index.d.ts
と書かねばならなかったのです(eの字が抜けていた)。
- "types": "./dist/indx.d.ts", // version 6.0.38 + "types": "./dist/index.d.ts", // version 6.0.40
先に「Angular8以下では、nehan6.0.38を使ってください」と書きましたが、つまりそれは、このバグったpackage.json
を使ったversion6.0.38を使ってください、ということです。なぜか?
理由は「そのバグがあるために、Typescript3.5以下でもエラーが出ないから」です。つまりバグのある古いバージョンを使うことで、Typescript3.5以下しか使えないAngular(<=8)でバグがでない、ということです。
実はnehanは各所にgetterと言われる機能を使っているのですが、実はこれ、Typescript3.6.3以降じゃないと次のようなエラーが出て、正しく使えなかった機能だったのです。
error TS1086: An accessor cannot be declared in an ambient context.
でもnehan6.0.38は正しいindex.d.ts
が読み込めていない状態だったので、幸運にも? Typescript(<=3.5)でもこのエラーが出なかったのです!
というわけで、Typescript3.5.3までしか使えないAngular8では、nehan(>=6.0.40)を使用できません…
ちなみにAngular9になると、Typescript3.6系列がサポートされるらしいので、そこからはnehanを6.0.40にアップデートできます。
VSCodeのマーケットプレースにvscode-typenovel
というTypeNovel用のVisual Studio Code拡張を公開しました。
インストール後は、*.tn
ファイルを編集するときに有効になります。
実際に動かすと、こんな感じです。
@
で始まる)と注釈($
で始まる)のインテリセンスちなみに$
で起動するインテリセンスは、もちろん組み込みタグだけではなく、ブロックで定義した制約の一覧も表示します。
バグや機能要望はGithubにて受け付けています。
vsce package
コマンドで拡張機能をパッケージ化するときに、.vscodeignore
ファイルでパッケージに含めないファイルを定義できるのですが、このファイルに.gitignore
を入れると、.gitignore
の中身を勝手に見て、その中に記述されたファイルもパッケージの対象外にしてしまうみたいです。
つまり.gitignore
の中に「gitの管理は不要だが、vs拡張のパッケージには必要」なファイルを記述してしまうと、それらがvs拡張にパッケージされず、拡張機能が動かないことがあります。
これを回避するのは簡単で、ようするに.vscodeignore
に.gitignore
を記述しなければよいのです(幸い.gitignore
は、.vscodeignore
に記述しなくてもパッケージ対象にはならないので)。
ちなみにパッケージされるファイルを事前に確認したい場合は、vsce package
をする前にvsce ls
とすると、パッケージされるファイル一覧を事前に確認することができます。
ローカルでインストールした.vsix
ファイルを、VSCodeのUIからアンインストールした場合、.vscode/extensions/<拡張機能のフォルダ>
が削除されないことがあります。
で、わかりにくいことに、この状態でVSCodeのUIから新しい.vsix
ファイルを読み込ませて上書きインストールさせようとすると、インストールは成功と表示されるのですが、実際には何も新しいファイルが展開されず、古いディレクトリだけが残っている状態になってしまいます。
つまりバグを修正して再インストールしても、修正されていない古い拡張が残り続けるので、まったく修正されていない状態になっているわけです。
この状態について、最初は「修正が正しくない」と判断して、ハマってしまいました。そこから「実は再インストール自体がなされておらず、古い拡張が残り続けているだけ」と気付くのに小一時間ぐらいかかりました。
ようするに「ローカルでインストール・アンインストールのテストするときは、UIを介さずに手動で拡張機能のディレクトリを削除する必要がある」ということになります。