ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English

TEI XMLのスタンドオフ注釈をインライン化する際の落とし穴とDOM操作による解決

デジタル延喜式は、延長5年(927年)に完成した律令の施行細則集『延喜式』を TEI (Text Encoding Initiative) XML で符号化し、Web上で閲覧・検索できるようにするプロジェクトです。国立歴史民俗博物館を中心に、校訂文・現代語訳・英訳を TEI でマークアップし、Nuxt.js(Vue.js)ベースのビューアで公開しています。 この開発の中で、TEI XML のスタンドオフ(standoff)注釈をインライン注釈に変換する処理において、XML の文書構造が崩壊するバグに遭遇しました。本記事では、その原因と DOM 操作ベースの解決策を記録します。 スタンドオフ注釈とは TEI XML では、テキスト中の校異(variant readings)を記録する方法として、スタンドオフ方式がよく使われます。デジタル延喜式では、複数の写本間のテキストの異同を <app> 要素で記録しており、テキスト中に <anchor> 要素で範囲を示し、対応する <app> 要素を別の場所に置く構造になっています。 <p> 前テキスト <anchor xml:id="app001"/> 校異対象のテキスト <anchor xml:id="app001e"/> 後テキスト </p> <!-- 別の場所に校異情報 --> <app from="#app001" to="#app001e"> <lem>校異対象のテキスト</lem> <rdg wit="#写本A">異なるテキスト</rdg> </app> この方式は、XML のネスト制約を回避できる利点があります。校異の範囲が要素境界をまたぐ場合(overlapping hierarchy)でも、anchor はどこにでも置けるためです。 インライン化の理由 XML ツリーと UI コンポーネントツリーの対応 デジタル延喜式のビューアは Vue.js で構築しています。Vue.js や React のようなコンポーネントベースのフレームワークでは、UI はツリー構造で記述されます。TEI XML もツリー構造なので、XML の各要素を UI コンポーネントに 1:1 でマッピングする再帰レンダリングが自然なアプローチになります。 <!-- TEI.vue: XML要素を再帰的にコンポーネントにマッピング --> <template> <component v-for="child in element.children" :is="getComponent(child.tagName)" :element="child" /> </template> この設計では、<app> 要素がテキスト中にインラインで存在すれば、ツリーの走査だけでレンダリングできます。 ...

「前近代日本-アジア関係資料デジタルアーカイブ」のビューアを試す

「前近代日本-アジア関係資料デジタルアーカイブ」のビューアを試す

概要 「前近代日本-アジア関係資料デジタルアーカイブ」が2025年7月25日に公開されました。 https://asia-da.lit.kyushu-u.ac.jp/ また、以下でビューアが公開されています。 https://github.com/localmedialabs/tei_comparative_viewer 本記事では、本ビューアを試した記録を共有します。 結果、以下のように、セルフホストすることができました。 https://tei-comparative-viewer.aws.ldas.jp/ 以下の「海東諸国紀」のXMLファイルを読み込んでいます。 https://asia-da.lit.kyushu-u.ac.jp/viewer/300 ローカルで起動する 以下に丁寧な説明がなされていますので、手順にしたがって起動させることができました。 https://github.com/localmedialabs/tei_comparative_viewer/blob/main/docs/SETUP.md サーバで起動する サーバで起動するにあたり、Dockerを用いて起動しました。 フォークしたリポジトリは以下です。 https://github.com/nakamura196/tei_comparative_viewer/tree/docker-traefik-setup 以下のようなファイルを用意しました。 FROM php:8.2-fpm # Install system dependencies RUN apt-get update && apt-get install -y \ git \ curl \ libpng-dev \ libonig-dev \ libxml2-dev \ zip \ unzip \ nodejs \ npm \ nginx \ supervisor # Clear cache RUN apt-get clean && rm -rf /var/lib/apt/lists/* # Install PHP extensions RUN docker-php-ext-install mbstring exif pcntl bcmath gd # Get latest Composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Set working directory WORKDIR /var/www # Copy existing application directory contents COPY . /var/www # Install dependencies RUN composer install --no-dev --optimize-autoloader # Install and build frontend assets RUN npm install && npm run build # Remove default nginx site RUN rm -f /etc/nginx/sites-enabled/default # Copy nginx config COPY docker/nginx/app.conf /etc/nginx/sites-available/app.conf RUN ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/ # Copy PHP-FPM config COPY docker/php/www.conf /usr/local/etc/php-fpm.d/www.conf # Copy supervisor config COPY docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf # Create necessary directories and set permissions RUN mkdir -p /var/log/supervisor \ && chown -R www-data:www-data /var/www \ && chmod -R 755 /var/www/storage \ && chmod -R 755 /var/www/bootstrap/cache # Generate key RUN php artisan key:generate # Optimize Laravel RUN php artisan config:cache && \ php artisan route:cache && \ php artisan view:cache # Expose port 80 EXPOSE 80 # Create PHP-FPM socket directory RUN mkdir -p /var/run/php # Start supervisord CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] services: app: build: context: . dockerfile: Dockerfile.prod.traefik container_name: tei_viewer_app restart: unless-stopped env_file: - .env.external volumes: - ./storage:/var/www/storage - ./public/assets:/var/www/public/assets networks: - traefik-network labels: - "traefik.enable=true" # HTTP router (redirects to HTTPS) - "traefik.http.routers.app-insecure.rule=Host(`xxx.yyy.zzz`)" - "traefik.http.routers.app-insecure.entrypoints=web" - "traefik.http.routers.app-insecure.middlewares=https-redirect" - "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https" # HTTPS router - "traefik.http.routers.app.rule=Host(`xxx.yyy.zzz`)" - "traefik.http.routers.app.entrypoints=websecure" - "traefik.http.routers.app.tls.certresolver=myresolver" - "traefik.http.services.app.loadbalancer.server.port=80" # Security headers - "traefik.http.middlewares.app-headers.headers.frameDeny=true" - "traefik.http.middlewares.app-headers.headers.contentTypeNosniff=true" - "traefik.http.middlewares.app-headers.headers.browserXssFilter=true" - "traefik.http.middlewares.app-headers.headers.referrerPolicy=strict-origin-when-cross-origin" - "traefik.http.middlewares.app-headers.headers.stsSeconds=31536000" - "traefik.http.middlewares.app-headers.headers.stsIncludeSubdomains=true" - "traefik.http.middlewares.app-headers.headers.stsPreload=true" # Apply middlewares - "traefik.http.routers.app.middlewares=app-headers" networks: traefik-network: external: true # Application Environment APP_ENV=production APP_DEBUG=false APP_KEY= APP_URL=https://xxx.yyy.zzz # Domain Configuration (used in docker-compose labels) DOMAIN=xxx.yyy.zzz # Database DB_CONNECTION=sqlite # Session and Cache SESSION_DRIVER=file CACHE_DRIVER=file # Logging LOG_CHANNEL=stack # Mail (if needed) MAIL_MAILER=smtp # Other Laravel configurations as needed #!/bin/bash echo "=== TEI Comparative Viewer Setup with External Traefik ===" # .env.externalファイルが存在しない場合は作成 if [ ! -f .env.external ]; then echo "Creating .env.external file..." cp .env.external.example .env.external # アプリケーションキーを生成 echo "Generating application key..." docker run --rm \ -v $(pwd):/var/www \ -w /var/www \ php:8.2-cli \ php artisan key:generate --env=production --show | sed 's/base64://' > app_key.tmp # 生成したキーを.env.externalに設定 APP_KEY=$(cat app_key.tmp) sed -i.bak "s/APP_KEY=/APP_KEY=base64:$APP_KEY/" .env.external rm app_key.tmp .env.external.bak echo "Application key generated successfully!" fi # 設定の確認 echo "" echo "⚠️ IMPORTANT: Please edit .env.external and configure the following:" echo " 1. DOMAIN=your-domain.com (your actual domain)" echo " 2. APP_URL=https://your-domain.com (with HTTPS)" echo " 3. ASSET_URL=https://your-domain.com (for proper asset loading)" echo "" echo "Note: This setup assumes you have an external Traefik instance running" echo "with the 'traefik-network' already created." echo "" read -p "Press Enter to continue with the current settings..." # .envファイルをロード export $(cat .env.external | grep -v '^#' | xargs) # traefik-networkが存在するか確認 echo "Checking if traefik-network exists..." if ! docker network ls | grep -q traefik-network; then echo "❌ Error: traefik-network not found!" echo "Please ensure your external Traefik is running with traefik-network created." echo "" echo "If you need to create the network manually:" echo " docker network create traefik-network" exit 1 fi echo "✅ traefik-network found!" # 必要なディレクトリを作成 echo "Creating necessary directories..." mkdir -p storage/app/public mkdir -p storage/framework/{cache,sessions,views} mkdir -p storage/logs mkdir -p bootstrap/cache # まず.envファイルをコピー echo "Copying environment file..." cp .env.external .env # Dockerイメージをビルド(キー生成なしのDockerfileを使用) echo "Building Docker images..." if ! docker compose -f docker-compose-external.yml build; then echo "❌ Docker build failed. Trying without cache..." docker compose -f docker-compose-external.yml build --no-cache fi # コンテナを起動 echo "Starting containers..." docker compose -f docker-compose-external.yml up -d # 起動確認 echo "Waiting for services to be ready..." sleep 15 # アプリケーションキーが設定されていない場合は生成 echo "Checking and generating application key if needed..." if docker compose -f docker-compose-external.yml exec app php artisan key:generate --show | grep -q "base64:"; then echo "Generating new application key..." docker compose -f docker-compose-external.yml exec app php artisan key:generate --force fi # 権限を設定 echo "Setting permissions..." docker compose -f docker-compose-external.yml exec app chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache || echo "Permission setting completed" # 設定キャッシュをクリア echo "Clearing configuration cache..." docker compose -f docker-compose-external.yml exec app php artisan config:clear docker compose -f docker-compose-external.yml exec app php artisan config:cache docker compose -f docker-compose-external.yml exec app php artisan route:clear # アプリケーションの状態確認 echo "Checking application status..." if docker compose -f docker-compose-external.yml ps | grep -q "Up"; then echo "✅ Application container is running!" else echo "❌ Application container may have issues. Check logs:" echo " docker compose -f docker-compose-external.yml logs app" fi echo "" echo "✅ Setup complete!" echo "" echo "Application should be available at your configured domain (HTTPS)" echo "(assuming your external Traefik is properly configured and SSL certificates are set up)" echo "" echo "To stop the application, run:" echo " docker compose -f docker-compose-external.yml down" echo "" echo "To view logs, run:" echo " docker compose -f docker-compose-external.yml logs -f app" echo "" echo "To check Traefik routing (if dashboard is accessible):" echo " Check your Traefik dashboard for the new routes" echo "" echo "📝 Next steps:" echo " 1. Edit .env.external with your actual domain settings" echo " 2. Update docker-compose-external.yml Traefik labels with your domain" echo " 3. Ensure DNS is properly configured for your domain" まとめ 間違っている点もあるかもしれませんが、参考になりましたら幸いです。 ...

Annotorious v2のpolygonツールを使って、polylineを作成する

Annotorious v2のpolygonツールを使って、polylineを作成する

概要 Annotorious v2のpolygonツールを使って、polylineを作成する方法の備忘録です。 背景 Annotorious v2のウェブサイトは以下です。 https://annotorious.github.io/getting-started/ 以下のように、polygonを記述することができます。 一方、同様の方法でpolylineを記述するツールは、以下のプラグインを含めて、提供されていないようでした。 https://github.com/annotorious/annotorious-v2-selector-pack カスタマイズ 以下のような多角形を作成した場合、 以下のようなJSONファイルが作成されます。 { "type": "Annotation", "body": [ { "type": "TextualBody", "value": "polygon", "purpose": "commenting" } ], "target": { "source": "https://www.e-codices.unifr.ch/loris/gau/gau-Fragment/gau-Fragment_frag001a.jp2/full/full/0/default/jpg", "selector": { "type": "SvgSelector", "value": "<svg><polygon points=\"3383.121337890625,1290.137451171875 945.135498046875,1658.426513671875 885.9696655273438,3003.352294921875 2508.54150390625,3348.424072265625 3485.021484375,2724.35791015625 2170.811767578125,2107.6337890625\" /></svg>" } }, "@context": "http://www.w3.org/ns/anno.jsonld", "id": "#c469b1a3-8902-4443-8f54-47df8bb87d7e" } 上記に対して、autoCloseのような変数を用意し、これがfalseの場合、polygonという文字列をpolylineに変更する処理を加えました。 anno.on("createAnnotation", function (selection: any) { if(!autoClose.value) { selection.target.selector.value = selection.target.selector.value.replace("polygon", "polyline"); } ... }); これにより、以下のように、polygonツールをベースとして、polygonとpolylineを使い分けることができます。 TEI/XMLでの記述 TEI/XMLでの多角形の記述例として、path要素を使用することができます。この場合、polygonであれば始点を終点の後に追加することで、多角形を表現することができます。 <facsimile> <surface ulx="0" uly="0" lrx="8176" lry="6132"> <graphic url="https://www.e-codices.unifr.ch/loris/gau/gau-Fragment/gau-Fragment_frag001a.jp2/full/full/0/default/jpg" /> <zone xml:id="layer_000" change="#ch_layer_000" n="layer_01" type="layer"> <zone xml:id="sign_layer_000_0000" change="#ch_sign_layer_000_0000" type="sign"> <!-- polygon --> <path points="1290.137451171875,3383.121337890625 1658.426513671875,945.135498046875 3003.352294921875,885.9696655273438 3348.424072265625,2508.54150390625 2724.35791015625,3485.021484375 2107.6337890625,2170.811767578125 1290.137451171875,3383.121337890625"></path> </zone> <zone xml:id="sign_layer_000_0001" change="#ch_sign_layer_000_0001" type="sign"> <!-- polyline --> <path points="1393.265625,5290.81005859375 1921.02783203125,3869.745849609375 2982.731689453125,3829.64013671875 3428.122802734375,4874.005859375 2683.244384765625,5741.7509765625 2138.024169921875,4582.19775390625"></path> </zone> </zone> </surface> </facsimile> アプリケーションによっては、始点と終点が一致しなくても閉じた図形を描くことがあるかもしれませんが、polygonとpolylineを使い分ける方法として、参考になりましたら幸いです。 ...

Editor.jsでインラインのマーカーツールで作成する

Editor.jsでインラインのマーカーツールで作成する

概要 Editor.jsでインラインのマーカーツールを作成する方法の備忘録です。 参考 以下のページが参考になりました。 https://editorjs.io/creating-an-inline-tool/ https://note.com/eveningmoon_lab/n/n638b9541c47c TypeScriptでの記述にあたっては、以下が参考になりました。 https://github.com/codex-team/editor.js/issues/900 実装 Nuxtで実装します。以下のmarker.tsを作成します。 import type { API } from "@editorjs/editorjs"; class MarkerTool { button: null | HTMLButtonElement; state: boolean; api: API; tag: string; class: string; // 静的メソッドで許可されるHTMLタグと属性を指定 static get sanitize() { return { mark: { class: "cdx-marker", }, }; } // インラインツールとしての振る舞いを定義 static get isInline() { return true; } constructor({ api }: { api: API }) { this.api = api; this.button = null; this.state = false; this.tag = "MARK"; this.class = "cdx-marker"; } // ボタン要素を作成し、SVGアイコンを設定 render() { this.button = document.createElement("button"); this.button.type = "button"; this.button.innerHTML = '<svg width="20" height="18"><path d="M10.458 12.04l2.919 1.686-.781 1.417-.984-.03-.974 1.687H8.674l1.49-2.583-.508-.775.802-1.401zm.546-.952l3.624-6.327a1.597 1.597 0 0 1 2.182-.59 1.632 1.632 0 0 1 .615 2.201l-3.519 6.391-2.902-1.675zm-7.73 3.467h3.465a1.123 1.123 0 1 1 0 2.247H3.273a1.123 1.123 0 1 1 0-2.247z"/></svg>'; this.button.classList.add(this.api.styles.inlineToolButton); return this.button; } // 選択されたテキストを <mark> タグで囲む surround(range: Range) { if (this.state) { this.unwrap(range); return; } this.wrap(range); } // テキストを <mark> タグでラップ wrap(range: Range) { const selectedText = range.extractContents(); const mark = document.createElement(this.tag); mark.className = this.class; // class 属性の追加 mark.appendChild(selectedText); range.insertNode(mark); this.api.selection.expandToTag(mark); } // <mark> タグを解除 unwrap(range: Range) { const mark = this.api.selection.findParentTag(this.tag); const text = range.extractContents(); mark?.remove(); range.insertNode(text); } // ツールの状態をチェック checkState() { const mark = this.api.selection.findParentTag(this.tag, this.class); this.state = !!mark; if (this.state) { this.button?.classList.add("cdx-marker--active"); } else { this.button?.classList.remove("cdx-marker--active"); } } } export default MarkerTool; 上記を以下のように呼び出します。 ...

Editor.jsのmax-widthを変更する

Editor.jsのmax-widthを変更する

概要 Editor.jsを使用する際、デフォルトでは左右に大きなマージンができます。これを解決する方法を紹介します。 方法 以下が参考になりました。 https://github.com/codex-team/editor.js/issues/1328 具体的には、以下を追加します。 .ce-block__content, .ce-toolbar__content { max-width: calc(100% - 80px) !important; } .cdx-block { max-width: 100% !important; } ソースコード全体は以下です。 <script setup lang="ts"> import EditorJS from "@editorjs/editorjs"; import type { OutputData } from "@editorjs/editorjs"; const blocks = ref<OutputData>({ time: new Date().getTime(), blocks: [ { type: "paragraph", data: { text: "大明副使蒋 承奉すらく 、 欽差督察総制提督浙江等処軍務各衙門 、近年以来、日本各島小民、仮るに買売を以て名と為し 、 しばしば中国辺境を犯し、居民を刼掠するを因となし、旨を奉じて 、 浙江等処承宣布政使司 に議行し、本職に転行して 、 親しく貴国に詣り面議せしめん等の因あり。", }, }, ], }); const editor = () => { new EditorJS({ holder: "editorjs", data: blocks.value, onChange: async (api) => { blocks.value = await api.saver.save(); }, }); }; editor(); </script> <template> <div style="background-color: aliceblue"> <div id="editorjs"></div> <hljson :content="blocks" /> </div> </template> <style> .ce-block__content, .ce-toolbar__content { max-width: calc(100% - 80px) !important; } .cdx-block { max-width: 100% !important; } pre { background-color: #f4f4f4; border: 1px solid #ccc; padding: 10px; } </style> 結果、以下のように、左右のマージンが小さくなりました。 ...

Nuxt 3 x Composition APIでLeaflet Marker Clusterを試す

Nuxt 3 x Composition APIでLeaflet Marker Clusterを試す

概要 以下の記事で、Nuxt 3でLeaflet Marker Clusterを試す方法を紹介しました。今回は、Composition APIを使った書き方に更新したので、その備忘録です。 インストール 以下をインストールします。 npm i leaflet leaflet.markercluster @vue-leaflet/vue-leaflet npm i -D @types/leaflet @types/leaflet.markercluster ソースコード 以下を参考にしてください。 https://github.com/nakamura196/nuxt3-demo/blob/main/components/map/MarkerCluster.vue まとめ TypeScriptに一部対応できていない点がありますが、参考になりましたら幸いです。

Nuxt3 x Vuetify x Cytoscape

Nuxt3 x Vuetify x Cytoscape

概要 Nuxt3とVuetifyを使用したサンプルリポジトリに、Cytoscapeの動作デモを追加しました。 https://github.com/nakamura196/nuxt3-demo 以下のページで動作確認いただけます。 https://nakamura196.github.io/nuxt3-demo/ インストール 以下を実行しました。 npm i cytoscape npm i @types/cytoscape ソースコード <template> <div id="view"> <v-btn class="ma-4" color="primary" v-on:click="addNode">push</v-btn> <div id="cy"></div> </div> </template> <script setup lang="ts"> import cytoscape from "cytoscape"; let cy: any = null; // = ref(null); //reactive({}); //: any const count: number = 0; // = ref(0); //reactive(0); const addNode = () => { cy.add([ { group: "nodes", data: { id: "node" + count }, position: { x: 300, y: 200 }, }, { group: "edges", data: { id: "edge" + count, source: "node" + count, target: "cat" }, }, ]); }; onMounted(() => { cy = cytoscape({ container: document.getElementById("cy"), boxSelectionEnabled: false, autounselectify: true, style: cytoscape .stylesheet() .selector("node") .css({ height: 80, width: 80, "background-fit": "cover", "border-color": "#000", "border-width": 3, "border-opacity": 0.5, content: "data(name)", "text-valign": "center", }) .selector("edge") .css({ width: 6, "target-arrow-shape": "triangle", "line-color": "#ffaaaa", "target-arrow-color": "#ffaaaa", "curve-style": "bezier", }), elements: { nodes: [ { data: { id: "cat" } }, { data: { id: "bird" } }, { data: { id: "ladybug" } }, { data: { id: "aphid" } }, { data: { id: "rose" } }, { data: { id: "grasshopper" } }, { data: { id: "plant" } }, { data: { id: "wheat" } }, ], edges: [ { data: { source: "cat", target: "bird" } }, { data: { source: "bird", target: "ladybug" } }, { data: { source: "bird", target: "grasshopper" } }, { data: { source: "grasshopper", target: "plant" } }, { data: { source: "grasshopper", target: "wheat" } }, { data: { source: "ladybug", target: "aphid" } }, { data: { source: "aphid", target: "rose" } }, ], }, layout: { name: "breadthfirst", directed: true, padding: 10, }, }); }); </script> <style scoped> #cy { width: 100%; height: 80%; position: absolute; background-color: lightcyan; } </style> まとめ 参考になりましたら幸いです。 ...

Vue.js: Splitpanesを用いた際のiframeを含むpaneへの対処方法

Vue.js: Splitpanesを用いた際のiframeを含むpaneへの対処方法

Splitpanesは、以下のように、ペイン(pane)分割・リサイズを可能にするVue.jsのライブラリです。 https://github.com/antoniandre/splitpanes このライブラリの利用にあたり、ペインにiframe要素を含む際、リサイズがうまくできないことがありました。これに対して、以下で対応方法が記載されていました。 https://github.com/antoniandre/splitpanes/pull/162 上記に記載がある通り、以下を追記することで、iframe要素を含むペインがあっても、正しくリサイズ操作を行うことができました。 .splitpanes--dragging .splitpanes__pane { pointer-events: none; } 同様のことでお困りの方の参考になりましたら幸いです。

VueUseを用いたテキスト選択(Nuxt3)

VueUseを用いたテキスト選択(Nuxt3)

概要 Nuxt3(Vue3)を用いたテキストの選択機能の実装にあたり、VueUseを使用してみましたので、その備忘録です。 https://vueuse.org/ デモ 以下のページからお試しいただけます。 https://nuxt3-demo-git-main-nakamura196.vercel.app/textSelection ソースコードは以下です。 https://github.com/nakamura196/nuxt3-demo/blob/main/pages/textSelection.vue インストール方法 以下のページに記載があります。 https://vueuse.org/guide/ 具体的な手順は、以下のとおりです。 npm i -D @vueuse/nuxt @vueuse/core // nuxt.config.ts export default defineNuxtConfig({ modules: [ '@vueuse/nuxt', ], }) まとめ テキスト選択以外にも、便利な機能が色々と使えるようなので、引き続き試してみたいと思います。

【Babylon.js x Vue】click eventをvueにわたす

【Babylon.js x Vue】click eventをvueにわたす

概要 以下のように、Babylon.jsでクリックしたmeshの名前を取得する方法を調査しました。 以下のチュートリアルを参考にしました。 https://doc.babylonjs.com/communityExtensions/Babylon.js+ExternalLibraries/BabylonJS_and_Vue/BabylonJS_and_Vue_2#passing-data-from-babylonjs-to-vue-using-callbacks デモページは以下です。 https://nakamura196.github.io/nuxt3-babylonjs/8/ ページのソースコードは以下です。 https://github.com/nakamura196/nuxt3-babylonjs/blob/main/pages/8/index.vue 実装方法 以下の箇所で、createSceneにcallback変数を渡しています。fpsCallbackの名前は修正したほうがよいかもしれません。 https://github.com/nakamura196/nuxt3-babylonjs/blob/5c33d2e6bcd1681df17f3f12fea3cd68fc645157/components/Scene8.vue#L10-L13 そして、createScene関数において、onPointerDownの結果を渡すようにしています。 https://github.com/nakamura196/nuxt3-babylonjs/blob/5c33d2e6bcd1681df17f3f12fea3cd68fc645157/scenes/Scene8.js#L44-L49 まとめ よりよい実装方法があるかもしれませんが、参考になりましたら幸いです。

Omeka ClassicをHeadless CMSとして使用してみる。

Omeka ClassicをHeadless CMSとして使用してみる。

概要 Omeka SおよびOmeka Classicは、デジタルアーカイブ構築および人文(情報)学研究において、とても便利なツールです。 https://omeka.org/ REST APIを標準搭載し、モジュールおよびプラグインの追加などによる高い拡張性を持ちます。またIIIF関連ツール、翻刻支援ツール、時空間情報を取り扱うツールなど、さまざまな既存資産を利用することができます。 一方、サイトの見た目を変更するテーマ開発などについては、PHPおよびOmekaに対する知識が求められ、比較的難易度が高いと(個人的に)感じています。この点について、昨今はバックエンドとフロンドエンドを分離したHeadless CMSという使い方も普及しつつあります。 そこでOmeka ClassicをHeadless CMSとして使用し、Nuxt 3を用いたフロントエンド開発を試みました。Omekaの活用方法の一例として参考になれば幸いです。 Omeka Classicの準備 APIの有効化 以下を参考に、APIの有効化を行います。 https://omeka.org/classic/docs/Admin/Settings/API_Settings/ Access-Control-Allow-Originヘッダーの追加 .htaccessファイルにAccess-Control-Allow-Originヘッダーを追加します。 Header set Access-Control-Allow-Origin "*" # 追加 # Omeka .htaccess: Apache configuration file # This file is required for Omeka to function correctly. # --------------- # # Error Reporting # ... Omeka ClassicのAPI 以下のページにAPIがまとめられています。 https://omeka.readthedocs.io/en/latest/Reference/api/index.html 例えば、以下のようなURLから、アイテムに対する簡易な検索が可能です。(Omeka Classicでは詳細な検索ができないようです。本格的な利用にあたっては、Omeka Sを使用する必要がありそうです。) https://omeka.aws.ldas.jp/api/items?search=被 フロントエンドの開発 今回は勉強を兼ねて、Nuxt 3とVuetify 3を使ってみました。2022-07-08時点において、アプリの完成度は大変低いが、Omeka ClassicをHeadless CMSとして利用し得ることを確認できました。 https://omekac.netlify.app/ まとめ Omeka(SおよびClassic)を用いたシステム開発における一例として、参考になりましたら幸いです。 ...

LeafletのVue3での使用例(座標範囲の取得を含む)

LeafletのVue3での使用例(座標範囲の取得を含む)

LeafletのVue3での使用例(座標範囲の取得を含む)を紹介するリポジトリを作成しました。 以下が動作例です。 https://static.ldas.jp/vue3-leaflet/ ソースコードは以下です。 https://github.com/ldasjp8/vue3-leaflet Vue3初学者のため、誤りなどがあるかもしれませんが、参考になりましたら幸いです。

Vue3でOpenSeadragonを使用するサンプルリポジトリを作成しました。

Vue3でOpenSeadragonを使用するサンプルリポジトリを作成しました。

Vue3でOpenSeadragonを使用するサンプルリポジトリを作成しました。 以下が動作例です。 https://static.ldas.jp/vue3-osd/ ソースコードは以下です。 https://github.com/ldasjp8/vue3-osd Vue3初学者のため、誤りなどがあるかもしれませんが、参考になりましたら幸いです。

Vuetifyでダイアログを開いたときにダイアログ内にフォーカステキストフィールドを設定する

Vuetifyでダイアログを開いたときにダイアログ内にフォーカステキストフィールドを設定する

以下が参考になりました。 https://stackoverflow.com/questions/59407003/set-focus-text-field-inside-dialog-when-dialog-opened 以下のように、ダイアログを開いてから少し時間を置いてから、$refsにアクセスするとうまくいきました。 watch: { dialog: function(value) { if (value) { setTimeout(() => { this.$refs.name.focus(); }, 200); } } }

カレンダー検索アプリを作成しました。

カレンダー検索アプリを作成しました。

概要 カレンダー形式で情報を表示するウェブアプリケーションを作成しました。以下、はてなブログの記事の一覧を対象にした表示例です。 https://static.ldas.jp/calendar/?u=https://nakamura196.github.io/json/calendar.json https://github.com/ldasjp8/calendar 以下にアクセスして、「例」ボタン、「追加」ボタンをクリックすることで、表示例をご確認いただけます。 https://static.ldas.jp/calendar/ 以下のような形式のjsonファイルのURLを引数に指定します。 https://nakamura196.github.io/json/calendar.json 以下、jsonファイルの作成方法の一例として、Excelファイルからの作成方法について説明します。 jsonファイルの作成方法 Excelファイルの作成 以下に示すようなExcelファイルを作成します。「metadata」と「items」の2つのシートを用意します。 https://docs.google.com/spreadsheets/d/14myDqZTxocwOT0Mw3ZzKLO81E6r15R-49oUh2dG9Rbo/edit?usp=sharing シート「metadata」 本シートには、A列に示す「description」「header」「footer」「link」を用意します。以下の画面に対応します。 「link」については、B列「literal」が表示文字列、C列「uri」がURLです。複数行を入力可能です。 シート「items」 本シートの以下の列が、予約済みの項目です。collectionsとdateは検索フォームに使用されます。 collections date label thumbnail url description G列以降の「Updated」などは、任意の項目です。 上記の項目は、以下のように対応します。なお、セル内に複数の値を入力したい場合には、「|(パイプ)」で区切ってください。 jsonファイルへの変換 以下のGoogle Colabを利用して、用意したExcelファイルをアップロードして、jsonファイルに変換します。 https://colab.research.google.com/drive/1aJKbJjK9Gu4SwDp6IfGCHNuhTH3pJ3hp 上記プログラムの実行後、ダウンロードされたJSONファイルをGitHub PagesやGist、レンタルサーバ等にアップロードし、そのURLを以下のカレンダー表示アプリに入力してください。 https://static.ldas.jp/calendar/ (参考)はてなブログの記事を対象とした利用例 はてなブログの記事を対象とした利用例として、はてなブログのAtomPub APIを用いて、上述したExcelファイルを生成するプログラムを作成しました。以下の記事を参考にしてください。

Nuxt.jsでvis.jsを使用する方法を紹介するGitHubリポジトリを作成しました。

Nuxt.jsでvis.jsを使用する方法を紹介するGitHubリポジトリを作成しました。

概要 Nuxt.jsでvis.jsを使用する方法を紹介するGitHubリポジトリを作成しました。 github.com 以下のページから、デモサイトをご確認いただけます。 https://nuxt-visjs.netlify.app/ 詳細 Nuxt.jsはVue.jsをより効果的に使えるフレームワークです。 またvis.jsは、ネットワーク図やタイムラインを作成することができる可視化ライブラリです。 visjs.org 今回、Nuxt.jsでvis.jsを使用する方法を紹介するGitHubリポジトリを作成しました。 「Timeline」「Network」「Graph2d」による基本的な可視化例を掲載しています。 Nuxt.jsを用いたvis.jsの利用に際して、お役にたてば幸いです。

Mirador 3をVueで使用する方法を紹介するリポジトリを作成しました。

Mirador 3をVueで使用する方法を紹介するリポジトリを作成しました。

Mirador 3をVueで使用する方法を紹介するリポジトリを作成しました。Vueを使ったアプリケーション開発の参考になれば幸いです。 github.com 本リポジトリの作成にあたっては、以下を参考にさせていただきました。 github.com なお、別のより簡単な方法として、UMD(Universal Module Definition)ビルドを利用する方法もあります。単純にMirador 3を利用したい場合には、以下の方法をおすすめします。 github.com 上記サイトでは、(今回紹介するリポジトリで示したように)「パッケージマネージャーを通じたインストールは、より柔軟なカスタマイズを可能とします。」と記載されています。 Installing through a package manager can give you more flexibility for customization.