カテゴリー: その他

  • Mozilla Firefoxノススメ

    デスクトップ、モバイル、エンタープライズ用 Firefox をダウンロード – Mozilla
    https://www.mozilla.org/ja/firefox

    Google Trends

    2004-現在

    昔はすごかった?

    現在のブラウザのシェアを見るとChrome一択だけど、最近複数画面/複数タブでピクチャーインピクチャー機能を使用するためにFirefoxをインストールした。

    Chromeでも拡張機能をインストールすればピクチャーインピクチャー機能を使用できるけれどマルチウインドウには対応していない。

    Picture-in-Picture Extension (by Google)

    https://chromewebstore.google.com/detail/picture-in-picture-extens/hkgfoiooedgoejojocmhlaklaeopbecg

    Support multiple window picture-in-picture #19

    https://github.com/GoogleChromeLabs/picture-in-picture-chrome-extension/issues/19

    マルチウインドウに対応するには大きな改修が必要らしい。データフォルダを用意して起動時に指定してやれば複数画面で使用できるみたいなことが書かれている?

    Firefox のピクチャーインピクチャーについて

    Firefox ならマルチウインドウで使用できる。

    https://support.mozilla.org/ja/kb/about-picture-picture-firefox

    動画にレイヤーが重ねられていてアクセサリーなどが配置されている場合は、ピクチャーインピクチャーのボタンが押せないので、Ctrl + Shift + ] キーのキーボードショートカットを使用している。

    追記:アドレスバーにもアイコンが用意されている。

    Firefox のダメなところについて

    アドレスバーでのサイト内検索機能が使いづらい。ブックマークでタグを設定してURLに「%s」を埋め込んでやれば検索できそうではあるけどChromeのアドレスバー/オムニバーの方がいい感じ。

    Firefoxでも過去の機能を復活させればショートカット+tab/spaceでサイト内検索できる模様。

    How do I add a custom search engine to Firefox desktop?

    https://stackoverflow.com/questions/77219507/how-do-i-add-a-custom-search-engine-to-firefox-desktop

    Re-enable a feature from the past

    1. Type in about:config in the address bar. Accept all the scary warnings.
    2. Search for browser.urlbar.update2.engineAliasRefresh.
    3. Press the + (on the right hand side).
    4. Now when you go to Settings > Search, there should be an Add button under the box which lists the various search engines available.
    5. Don’t let this post get popular, otherwise this method will also be deprecated to make Firefox better than ever!
    検索エンジン名別名URL
    Amazon Prime Videopvhttps://www.amazon.co.jp/s?k=%s&i=instant-video
    Netflixnfhttps://www.netflix.com/search?q=%s
    YouTubeythttps://www.youtube.com/results?search_query=%s
    Wikipediawphttps://ja.wikipedia.org/w/index.php?search=%s

    とりあえず4つ登録した。

    あとは、Firefoxは標準機能でフォントの指定を強制できるのはいいけどレンダリング結果が汚い。フォントがにじむの何とかならないのか調査中…

    Firefox Color

    Chromeの画面なのかFirefoxの画面なのかぱっと見で区別できないので、ブラウザの「テーマ」を初めて設定してみた。

    https://color.firefox.com/

    しばらくオレンジ色で使ってみる。

    フォクすけ

    https://ja.wikipedia.org/wiki/フォクすけ

    フォクすけ、ぐぐっても古い情報しかヒットしないですね… 終わったんですね。

    ブラウザ戦争

    https://ja.wikipedia.org/wiki/ブラウザ戦争

    ブラウザ戦争は死語ですか?そうですかー

  • How to unregister all installed service workers in Chrome

    https://www.stefanjudis.com/snippets/how-to-unregister-all-installed-service-workers-in-chrome

    サービスワーカー API

    サービスワーカーは、基本的にウェブアプリケーション、ブラウザー、そして(もし繋がっていれば)ネットワークの間に介在するプロキシサーバーのように振る舞います。これは、よりよいオフラインの操作性を可能にするように意図されており、ネットワークのリクエストに介在してネットワークの使用可否の状況に基づいて適切な対応を取ったり、サーバー上にある資産を更新したりします。また、プッシュ通知やバックグラウンド同期の API 群へのアクセスもできるようになります。

    https://developer.mozilla.org/ja/docs/Web/API/Service_Worker_API

    よりよいオフラインの操作性?そんなのはいらん。

    やったこと

    1. Chromeで以下のページを開く
      chrome://serviceworker-internals/
      →登録されているサービスワーカーが表示される
    2. 「F12」キーを押下してディベロッパーツールを開く
    3. ディベロッパーツールのコンソールで以下を実行する
      $$('.unregister').forEach(b => b.click())
      →サービスワーカーが削除される

    補足

    他のタブで開いているサイトや使っている拡張機能のサービスワーカーは削除されない。

    たまにゴミ掃除したくなるけどサービスワーカーを削除しても体感できるようなパフォーマンス改善が見込めるわけではないので悪しからずご了承くださいませ…

  • Quickstart: Deploy RESTful API application to Azure Spring Apps

    https://learn.microsoft.com/ja-jp/azure/spring-apps/quickstart-deploy-restful-api-app

    テスト

    実際にデプロイしたURLがこれ。
    https://deploy-test-simple-todo-api.azuremicroservices.io/swagger-ui/index.html (※削除済)

    便利なようで便利じゃないやつ多すぎて草。

    Raspberry Pi がいいね。

  • クイックスタート: 初めての Web アプリケーションを Azure Spring Apps にデプロイする

    https://learn.microsoft.com/ja-jp/azure/spring-apps/quickstart-deploy-web-app

    テスト

    実際にデプロイしたURLがこれ。
    https://deploy-test-simple-todo-web.azuremicroservices.io/ (※削除済)

    サンプルアプリひどい。リロードすると画面がw

    Azure Spring Apps と Azure Database for PostgreSQL が高すぎて草。期間限定の無料クレジット200ドルがなければやってみることもしないだろうなぁ…

    Raspberry Pi がいいね。

  • ひげおやじ物語

    Azure Static Web Apps

    ひげおやじ物語
    https://static-web.kozawa.tokyo/ ※削除済

    無料枠でAzure DevOpsのパイプラインからAzure Static Web Appsにデプロイしようとすると以下のエラーとなる。

    [error]No hosted parallelism has been purchased or granted. To request a free parallelism grant, please fill out the following form https://aka.ms/azpipelines-parallelism-request

    エラーメッセージのURLからリクエストを投げても音沙汰なし。

    「Please consider that it could take 2-3 business days to proceed the request. We are working on improving this process at the moment. Sorry for the inconvenience.」

    と書かれているけれど1週間経過しても権限付与してもらえない。

    Azure DevOpsは諦めてGitHubのリポジトリからデプロイした。SSHキーの生成や設定が必要になった。

    Azure Blob Storage

    ひげおやじ物語
    https://blob-storage.kozawa.tokyo/ ※削除済

    Azure Blob Storageの静的Webサイトでカスタムドメインを使用する場合は、「Front Door と CDN」でドメインを認証してプライベートエンドポイント接続してやらないとhttpsでアクセスができない。めんどくさい作業が発生した。

    カスタム ドメインを Azure Blob Storage エンドポイントにマップする
    https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-custom-domain-name

    ホスティング環境

    Static Web Apps、Blob Storage、どちらも画面ポチポチでサーバ証明書を生成してくれるのは便利だけど、初めてやってみると何かしら引っかかるやん。しかも、Front Door、Storage、Networkで課金が発生している。Front Door高いな。結局、無料枠だけでは難しい気がする。

    Raspberry Pi がいいね。

  • SVGファイルで「🌀」テキストを回転させたい

    備忘:

    SVGファイルでテキスト1文字を綺麗に回転させることはできない。Androidで描画が欠落することを確認した。Windowsは知らん。テキストだけで完結したいけど無理そう…

    <svg version="1.1"
    	xmlns="http://www.w3.org/2000/svg"
    	xmlns:xlink="http://www.w3.org/1999/xlink"
    	height="50px"
    	width="50px"
    	viewBox="0 0 50 50">
    	<style>
    		#typhoon {
    			dominant-baseline: text-before-edge;
    			font-size: xxx-large;
    		}
    	</style>
    	<animateTransform
    		xlink:href="#typhoon"
    		attributeName="transform"
    		type="rotate"
    		from="0 25 25"
    		to="360 25 25"
    		dur="5s"
    		repeatCount="indefinite"
    	/>
    	<text id="typhoon">🌀</text>
    </svg>
    ChatGPT

    「🌀」という1文字の回転中心座標を正確に指定することは、環境やフォントによって異なるため、困難です。JSを使用するとSVGファイルをimgタグで埋め込むことができなくなります。さらに、SVGファイル内ではCSS/@keyframesが正常に動作しないことがあります。

    この問題に対する直接的な解決策は限られているようですが、一般的なアプローチとしては、素直に画像を回転させる方法が考えられます。回転したいテキストやシンボルを画像として作成し、必要な角度で回転させることで、一貫した表示が可能です。

    また、回転が必要なテキストやシンボルを固定された角度で使用する場合、事前に回転済みの画像を用意しておくことも考えられます。

    状況によって最適な方法が異なるかもしれませんが、回転の正確さやクロスブラウザの互換性を考慮しながら、適切な方法を選択することが重要です。

    	<text
    		id="typhoon"
    		x="50%"
    		y="50%"
    		text-anchor="middle"
    		dominant-baseline="central"
    	>🌀</text>

    テキストの中心座標を求めるのではなく、テキストを上下左右中央に揃えても、結局固定値のハードコーディングが残存してしまい、フォントサイズが変わると描画が欠落する可能性が考えられます。

    むずかしいなあ…

  • 常識の範囲内で、自由につかってください(2回目)

    備忘:

    常識の範囲内で、自由につかってください①
    https://twitter.com/JP_GHIBLI/status/1685606160580198400
    常識の範囲内で、自由につかってください②
    https://twitter.com/JP_GHIBLI/status/1685606411752108033
    常識の範囲内で、自由につかってください③
    https://twitter.com/JP_GHIBLI/status/1685606915164749824

    C. SVGアニメーション

    Aは、「常識の範囲内で、自由につかってください③」ツイートに添付されていたオリジナルファイル。Bは、Aをファイル変換サイトで変換したファイル。Cは、「常識の範囲内で、自由につかってください①②」に添付されている画像ファイルから作成したファイル。背景透過済み。クリックイベントを実装済み。マウスポインターが矢印のままだ今気づいた。軽微な不備1。

    SVGファイルにはCSS/JSを内包できる。ページ内に同じSVGファイルを複数配置してもidの重複などは発生しない。JSのグローバル領域が汚染されることもない。画像として表示する場合は、普通に<img>タグが使えるが、JSを実行する場合は、<object>タグで埋め込む必要がある。

  • 世界時価総額ランキング10(1989 – 2023)

    時を戻そう。日本がんばれ〜

    ツール:Flourish https://flourish.studio/
    データ:Bing AI
    データ加工:Excel ピボットテーブル + XLOOKUP関数

    新しいBingでデータを集めた。一括でデータ収集させてCSV形式でダウンロードしたいのだけど現状では無理っぽい。1年ごとに検索してExcelにコピペする手作業が発生してる。しかも、チャットのレスが遅い… 持ってないデータを検索して解析してゴニョゴニョしてて時間がかかるとは思うもののまだまだ使いづらい。

    1990年と1991年はデータが見つからない。データも怪しいしランキング順位が大きく変わる年はグラフが正しく表示されない。逆再生したかっただけだからいいけど…

    時間は戻らないらしいな。時間とは何か?それが問題だ?

    正しそうなやつも貼っとく

  • オセロ by ChatGPT

    ChatGPTで生成したバグってるソースを手直した。変数の宣言がなかったり関数ごと欠損しているソースをデバッグするところからスタートして何となく動くようになったので投稿。初めテトリスを作ろうとしたけど、まともに動かない状態から抜け出せずに挫折… オセロは、テトリスの半分以下で300行程度。

    黒の手番です

    備忘:CSSに一番苦戦した。borderとborder-radiusが同時に指定できない。border-radiusでコマを描くとborderで描いた枠線が欠落してしまう。最終的にはコマの白黒画像を用意してbackground-imageで読み込むように修正した。


    ご参考:
    プロンプト:オセロゲームのサンプルをHTML/CSS/JavaScriptで作成してください。
    ChatGPT:はい、以下にHTML、CSS、JavaScriptで書かれたオセロゲームのサンプルを用意しました。
    ・・・

    <!DOCTYPE html>
    <html>
    
    <head>
    	<meta charset="UTF-8">
    	<title>オセロゲーム</title>
    	<style>
    		.container {
    			display: flex;
    			flex-wrap: wrap;
    			width: 320px;
    			height: 320px;
    			margin: 0 auto;
    		}
    
    		.cell {
    			width: 40px;
    			height: 40px;
    			background-color: green;
    			border: 1px solid black;
    			box-sizing: border-box;
    			cursor: pointer;
    		}
    
    		.cell.black {
    			background-color: black;
    		}
    
    		.cell.white {
    			background-color: white;
    		}
    
    		.turn {
    			margin-top: 10px;
    			text-align: center;
    			font-size: 20px;
    		}
    
    		.win {
    			margin-top: 10px;
    			text-align: center;
    			font-size: 30px;
    			font-weight: bold;
    		}
    	</style>
    </head>
    
    <body>
    	<div class="container"></div>
    	<div class="turn">黒の手番です</div>
    	<div class="win"></div>
    	<script>
    
    		const WHITE = 1;
    		const BLACK = -1;
    		const EMPTY = 0;
    
    		// ゲームボードのサイズ
    		const BOARD_SIZE = 8;
    
    		// ゲームボードの初期化
    		let board = [];
    		for (let i = 0; i < BOARD_SIZE; i++) {
    			board[i] = [];
    			for (let j = 0; j < BOARD_SIZE; j++) {
    				board[i][j] = 0;
    			}
    		}
    		board[3][3] = board[4][4] = 1;
    		board[3][4] = board[4][3] = -1;
    
    		// 現在のプレイヤー(1:黒, -1:白)
    		let currentPlayer = 1;
    
    		// 盤面を描画する関数
    		function drawBoard() {
    			const container = document.querySelector('.container');
    			container.innerHTML = '';
    			for (let i = 0; i < BOARD_SIZE; i++) {
    				for (let j = 0; j < BOARD_SIZE; j++) {
    					const cell = document.createElement('div');
    					cell.className = 'cell';
    					if (board[i][j] === 1) {
    						cell.classList.add('black');
    					} else if (board[i][j] === -1) {
    						cell.classList.add('white');
    					}
    					cell.dataset.row = i;
    					cell.dataset.col = j;
    					cell.addEventListener('click', handleClick);
    					container.appendChild(cell);
    				}
    			}
    		}
    
    		drawBoard();
    
    		// クリックイベントのハンドラー関数
    		function handleClick(event) {
    			const row = parseInt(event.target.dataset.row);
    			const col = parseInt(event.target.dataset.col);
    			if (board[row][col] !== 0) {
    				return;
    			}
    			const directions = [
    				{ x: 0, y: -1 }, // 上
    				{
    					x: 0, y
    
    1 }, // 下
    				{ x: -1, y: 0 }, // 左
    				{ x: 1, y: 0 }, // 右
    				{ x: -1, y: -1 }, // 左上
    				{ x: 1, y: -1 }, // 右上
    				{ x: -1, y: 1 }, // 左下
    				{ x: 1, y: 1 } // 右下
    			];
    			let canPlace = false;
    			for (let d = 0; d < directions.length; d++) {
    				const dx = directions[d].x;
    				const dy = directions[d].y;
    				let x = row + dx;
    				let y = col + dy;
    				let flipCount = 0;
    				while (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE) {
    					if (board[x][y] === -currentPlayer) {
    						flipCount++;
    						x += dx;
    						y += dy;
    					} else if (board[x][y] === currentPlayer) {
    						if (flipCount > 0) {
    							canPlace = true;
    						}
    						break;
    					} else {
    						break;
    					}
    				}
    				if (canPlace) {
    					break;
    				}
    			}
    			if (!canPlace) {
    				return;
    			}
    			board[row][col] = currentPlayer;
    			event.target.classList.add(currentPlayer === 1 ? 'black' : 'white');
    			event.target.removeEventListener('click', handleClick);
    			flipStones(row, col);
    			currentPlayer = -currentPlayer;
    			const turnEl = document.querySelector('.turn');
    			turnEl.textContent = ${ currentPlayer === 1 ? '黒' : '白' } の手番です;
    			if (!hasPlaceableCell()) {
    				currentPlayer = -currentPlayer;
    				if (!hasPlaceableCell()) {
    					const winEl = document.querySelector('.win');
    					const blackCount = countStones(1);
    					const whiteCount = countStones(-1);
    					if (blackCount > whiteCount) {
    						winEl.textContent = '黒の勝ち!';
    					} else if (blackCount < whiteCount) {
    						winEl.textContent = '白の勝ち!';
    					} else {
    						winEl.textContent = '引き分け!';
    					}
    				} else {
    					turnEl.textContent = ${ currentPlayer === 1 ? '黒' : '白' } はパスです;
    				}
    			}
    		}
    
    		// 石を返す関数
    		function flipStones(row, col) {
    			const directions = [
    				{ x: 0, y: -1 }, // 上
    				{ x: 0, y: 1 }, // 下
    				{ x: -1, y: 0 }, // 左
    				{ x: 1, y: 0 }, // 右
    				{ x: -1, y: -1 }, // 左上
    				{ x: 1, y: -1 }, // 右上
    				{ x: -1, y: 1 }, // 左下
    				{ x: 1, y: 1 } // 右下
    			];
    			for (let d = 0; d < directions.length; d++) {
    				const dx = directions[d].x;
    
    				const dy = directions[d].y;
    				let x = row + dx;
    				let y = col + dy;
    				let flipCount = 0;
    				while (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE) {
    					if (board[x][y] === -currentPlayer) {
    						flipCount++;
    						x += dx;
    						y += dy;
    					} else if (board[x][y] === currentPlayer) {
    						if (flipCount > 0) {
    							for (let i = 1; i <= flipCount; i++) {
    								const flipX = row + i * dx;
    								const flipY = col + i * dy;
    								board[flipX][flipY] = currentPlayer;
    								const flipEl = document.querySelector(`[data-row="${flipX}"][data-col="${flipY}"]`);
    								flipEl.classList.remove(currentPlayer === 1 ? 'white' : 'black');
    								flipEl.classList.add(currentPlayer === 1 ? 'black' : 'white');
    							}
    						}
    						break;
    					} else {
    						break;
    					}
    				}
    			}
    		}
    
    		// 置けるセルがあるかどうかを判定する関数
    		function hasPlaceableCell() {
    			for (let i = 0; i < BOARD_SIZE; i++) {
    				for (let j = 0; j < BOARD_SIZE; j++) {
    					if (board[i][j] === 0 && canPlaceStone(i, j)) {
    						return true;
    					}
    				}
    			}
    			return false;
    		}
    
    		// 石の数を数える関数
    		function countStones(player) {
    			let count = 0;
    			for (let i = 0; i < BOARD_SIZE; i++) {
    				for (let j = 0; j < BOARD_SIZE; j++) {
    					if (board[i][j] === player) {
    						count++;
    					}
    				}
    			}
    			return count;
    		}
    
    		// ゲームをリセットする関数
    		function resetGame() {
    			board = initializeBoard();
    			currentPlayer = 1;
    			const cells = document.querySelectorAll('.cell');
    			for (let i = 0; i < cells.length; i++) {
    				cells[i].classList.remove('black', 'white');
    				cells[i].addEventListener('click', handleClick);
    			}
    			const turnEl = document.querySelector('.turn');
    			turnEl.textContent = '黒の手番です';
    			const winEl = document.querySelector('.win');
    			winEl.textContent = '';
    		}
    
    		function canPlaceStone(row, col, color) {
    			if (board[row][col] !== EMPTY) {
    				return false;
    			}
    			for (let drow = -1; drow <= 1; drow++) {
    				for (let dcol = -1; dcol <= 1; dcol++) {
    					if (drow === 0 && dcol === 0) {
    						continue;
    					}
    					let r = row + drow;
    					let c = col + dcol;
    					let flipped = false;
    					while (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE) {
    						if (board[r][c] === EMPTY) {
    							break;
    						}
    						if (board[r][c] === color) {
    							if (flipped) {
    								return true;
    							} else {
    								break;
    							}
    						}
    						flipped = true;
    						r += drow;
    						c += dcol;
    					}
    				}
    			}
    			return false;
    		}
    
    		function initializeBoard() {
    			for (let row = 0; row < BOARD_SIZE; row++) {
    				for (let col = 0; col < BOARD_SIZE; col++) {
    					const cell = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`);
    					cell.classList.remove('black', 'white');
    					if ((row === 3 && col === 3) || (row === 4 && col === 4)) {
    						cell.classList.add('white');
    						board[row][col] = WHITE;
    					} else if ((row === 3 && col === 4) || (row === 4 && col === 3)) {
    						cell.classList.add('black');
    						board[row][col] = BLACK;
    					} else {
    						board[row][col] = EMPTY;
    					}
    				}
    			}
    		}
    
    		// 初期化
    		let board = initializeBoard();
    		let currentPlayer = 1;
    		const cells = document.querySelectorAll('.cell');
    		for (let i = 0; i < cells.length; i++) {
    			cells[i].addEventListener('click', handleClick);
    		}
    		const resetBtn = document.querySelector('.reset');
    		resetBtn.addEventListener('click', resetGame);
    		const turnEl = document.querySelector('.turn');
    		turnEl.textContent = '黒の手番です';
    }) ();
    
    	</script>
    </body>
    
    </html>

    ※インデントなど整形済み