2022年12月26日月曜日
Google Oneを安く契約する方法
2022年11月29日火曜日
Google Formsでドロップダウン形式の日付を毎日変更する
背景
ソースコート
function updateForm() {
// 毎日実行するトリガを設定
setTrigger( arguments.callee.name );
var forms = FormApp.getActiveForm();
var items = forms.getItems();
var table = [
{'title':'発症日','start':-14 ,'end':0 }, // 二週間前から今日まで
{'title':'診察希望日1','start':7 ,'end':1 }, // 7日後から明日まで(逆順)
{'title':'診察希望日2','start':1 ,'end':7 } // 明日から7日後まで
];
// 途中に日付が変わると面倒なのでここで取得
var today = new Date();
items.forEach( (item) =>{
table.forEach( (range) =>{
// リスト形式で質問がテーブルのtitleと一致したときに処理
if( item.getType() == FormApp.ItemType.LIST && item.getTitle() == range.title )
{
item = item.asListItem();
var choices = [];
// テーブルの内容に従い選択肢を追加
var dir = (range.start<range.end)?1:-1;
for( var i = range.start; (range.start<=i&&i<=range.end)||(range.end<=i&&i<=range.start); i += dir )
{
var day = new Date( today );
day.setDate(today.getDate() + i);
choices.push( item.createChoice(formatDate( day, "yyyy/MM/dd" )));
}
item.setChoices(choices);
}
});
});
}
function formatDate (date, format) {
format = format.replace(/yyyy/g, date.getFullYear());
format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2));
format = format.replace(/dd/g, ('0' + date.getDate()).slice(-2));
format = format.replace(/HH/g, ('0' + date.getHours()).slice(-2));
format = format.replace(/mm/g, ('0' + date.getMinutes()).slice(-2));
format = format.replace(/ss/g, ('0' + date.getSeconds()).slice(-2));
format = format.replace(/SSS/g, ('00' + date.getMilliseconds()).slice(-3));
return format;
}
function setTrigger(name) {
// 念のため既存のトリガはすべて削除してから次のトリガーを設定する
var triggers = ScriptApp.getProjectTriggers();
triggers.forEach( (trigger) =>{
if( trigger.getHandlerFunction() == name )
{
ScriptApp.deleteTrigger(trigger);
}
});
var setTime = new Date();
setTime.setDate(setTime.getDate() + 1)
setTime.setHours(12);
setTime.setMinutes(00);
ScriptApp.newTrigger(name).timeBased().at(setTime).create();
}
フォームに追加したプルダウン
2022年11月20日日曜日
GoogleTakeoutでダウンロードしたGoogleフォト画像のファイルタイムスタンプを修正するプログラム
以前の記事( https://blog.hikozaru.com/2022/11/googletakeoutgoogle.html)で紹介した作業をもう少し簡単に実行できるようにプログラムを作成したのでそのソースコードを公開する。いろいろ手を抜くために邪道なこと(変数名に日本語を使う)を行っているが、気になる人は好きに修正するとよい。プロジェクト全体が欲しい方はここから、実行できるものが欲しい方はここからダウンロードしてください。
プログラムの大まかな処理の流れは以下です。
- 指定されたファイルもしくは指定されたフォルダ配下のファイルすべてを処理
ただし、JSONファイルは処理をスキップ - ファイル名の後ろに".json"を付加したファイルが存在していたらデシリアライズして撮影日時を取得
- 撮影日時はunixtimeなのでutcを経由してローカル時間に変換
- ファイルの作成日時と更新日時を変更、その処理が成功したらJSONファイルも同様に作成日時と更新日時を変更
- 結果をDataGridに出力
まずはCSファイル
using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Windows;
namespace PhotosTimestamp
{
[DataContract]
public class ImageInfo
{
[DataMember(Name = "photoTakenTime")]
public PhotoTakenTime PhotoTakenTime { get; set; }
}
[DataContract]
public class PhotoTakenTime
{
[DataMember(Name = "timestamp")]
public string timstamp { get; set; }
}
public partial class MainWindow : Window
{
enum E_RESULT
{
更新,
失敗,
SKIP
};
class Result
{
public int 番号 { get; set; }
public E_RESULT 結果 { get; set; }
public string ファイル名 { get; set; }
public string エラーメッセージ { get; set; }
public string 例外メッセージ { get; set; }
}
const string HowToUse
= "Google TakeoutでダウンロードしたZIPファイルを展開し、展開されたファイル/フォルダをドラッグ&ドロップして下さい。\n"
+ "コマンドライン引数やプログラムアイコンへのドラッグ&ドロップでもファイル/フォルダ指定可能です。\n"
+ "画像ファイルにJSONファイル(例:IMG_1234.PNGに対しIMG_1234.PNG.json)がある場合に処理を行います。\n"
+ "TakeoutでZIPファイルが分割された場合には一つのフォルダに統合してください、画像ファイルとJSONファイルが同一のZIPに格納されるとは限りません。\n"
+ "JSONファイルに記録されているphotoTakenTime.timestampをLocal時間に変換し、画像ファイルとJSONファイルの作成日時と更新日時に設定します。";
public MainWindow()
{
InitializeComponent();
var resultList = new ObservableCollection();
resultDataGrid.ItemsSource = resultList;
}
// 引数で指定されたときは即実行
private void Window_ContentRendered(object sender, EventArgs e)
{
string[] args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{
Thread thread = new Thread(new ParameterizedThreadStart(WorkerThread));
thread.Start(args.Skip(1).ToArray());
}
else
{
ddPanel.AllowDrop = true;
status.Content = HowToUse;
}
}
// Drag&Drop関連処理
private void ddPanel_PreviewDragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.None;
if (e.Data.GetDataPresent(System.Windows.DataFormats.FileDrop, true))
{
e.Effects = DragDropEffects.Copy;
e.Handled = e.Data.GetDataPresent(DataFormats.FileDrop);
}
}
private void ddPanel_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
Thread thread = new Thread(new ParameterizedThreadStart(WorkerThread));
thread.Start(e.Data.GetData(DataFormats.FileDrop));
}
}
// UIが固まらないようにThreadで処理
void WorkerThread(object arg)
{
Dispatcher.Invoke((Action)(() =>
{
ddPanel.AllowDrop = false;
status.Content = "処理開始";
var dataList = resultDataGrid.ItemsSource as ObservableCollection;
dataList.Clear();
}));
DoEntries((string[])arg);
Dispatcher.Invoke((Action)(() =>
{
ddPanel.AllowDrop = true;
status.Content = "処理が終了しました。\n" + HowToUse;
}));
}
void DoEntries(string[] args)
{
foreach (string arg in args)
{
if (File.Exists(arg) == true)
{
DoFile(arg);
}
else
{
try
{
foreach (var path in Directory.GetFiles(arg, "*", System.IO.SearchOption.AllDirectories))
{
DoFile(path);
}
}
catch (Exception e)
{
Dispatcher.Invoke((Action)(() =>
{
var dataList = resultDataGrid.ItemsSource as ObservableCollection;
dataList.Add(new Result() { 番号=dataList.Count+1, 結果=E_RESULT.失敗, ファイル名 = arg, エラーメッセージ="ファイルまたはフォルダが見つかりませんでした。",例外メッセージ = e.ToString() });
}));
}
}
}
}
void DoFile(string targetFilename)
{
var jsonFilename = targetFilename + ".json";
if (string.Compare(".json", System.IO.Path.GetExtension(targetFilename), true) == 0)
{
Dispatcher.Invoke((Action)(() =>
{
status.Content = targetFilename;
}));
}
else if (System.IO.File.Exists(jsonFilename))
{
DateTime photoTakenTime;
try
{
using (var stream = new FileStream(jsonFilename, FileMode.Open, FileAccess.Read))
{
var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(ImageInfo));
var imageInfo = (ImageInfo)serializer.ReadObject(stream);
var unixtime = imageInfo.PhotoTakenTime.timstamp;
photoTakenTime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(unixtime)).LocalDateTime;
}
var imageFile = new System.IO.FileInfo(targetFilename);
var jsonFile = new System.IO.FileInfo(jsonFilename);
var results = new Result[] {
new Result(){ 結果=E_RESULT.SKIP,ファイル名=targetFilename,エラーメッセージ="",例外メッセージ="" },
new Result(){ 結果=E_RESULT.SKIP,ファイル名=jsonFilename,エラーメッセージ="画像ファイルの更新に失敗したときはJSONファイルも更新しません。",例外メッセージ="" }
};
foreach (var result in results)
{
try
{
var file = new System.IO.FileInfo(result.ファイル名);
file.CreationTime = photoTakenTime;
file.LastWriteTime = photoTakenTime;
result.結果 = E_RESULT.更新;
result.エラーメッセージ = "";
result.例外メッセージ = "";
}
catch (Exception e)
{
result.結果 = E_RESULT.失敗;
result.エラーメッセージ = "タイムスタンプの更新に失敗しました。";
result.例外メッセージ = e.ToString();
break;
}
}
Dispatcher.Invoke((Action)(() =>
{
status.Content = targetFilename;
var dataList = resultDataGrid.ItemsSource as ObservableCollection;
foreach (var result in results)
{
result.番号 = dataList.Count + 1;
dataList.Add(result);
}
}));
}
catch(Exception e)
{
Dispatcher.Invoke((Action)(() =>
{
status.Content = targetFilename;
var dataList = resultDataGrid.ItemsSource as ObservableCollection;
dataList.Add(new Result() { 番号 = dataList.Count + 1, 結果 = E_RESULT.失敗, ファイル名 = jsonFilename, エラーメッセージ = "タイムスタンプの取得に失敗しました。", 例外メッセージ = e.ToString() });
}));
}
}
else
{
Dispatcher.Invoke((Action)(() =>
{
status.Content = targetFilename;
var dataList = resultDataGrid.ItemsSource as ObservableCollection;
dataList.Add(new Result() { 番号 = dataList.Count + 1, 結果 = E_RESULT.SKIP, ファイル名 = targetFilename, エラーメッセージ = "*.JSONファイルがありません。", 例外メッセージ = "" });
}));
}
}
}
}
次はXAMLファイル<window contentrendered="Window_ContentRendered" height="450" mc:ignorable="d" title="Google Takeoutでダウンロードした画像のタイムスタンプをJSONファイルを参考に修正します。" width="1200" x:class="PhotosTimestamp.MainWindow" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:PhotosTimestamp" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<dockpanel allowdrop="false" drop="ddPanel_Drop" name="ddPanel" previewdragover="ddPanel_PreviewDragOver">
<label dockpanel.dock="Bottom" name="status">
<datagrid dockpanel.dock="Top" isreadonly="True" name="resultDataGrid">
</datagrid></label></dockpanel>
</window>
2022年11月14日月曜日
GoogleTakeoutでダウンロードしたGoogleフォト画像のファイルタイムスタンプを修正する
概要
①PowerShellのスクリプトを実行できるようにする
PowerShell 管理者権限として実行します。ウインドウズメニューを開いた状態で「PowerSell」と入力すると見つかるので、「管理者として実行する」を選択してください。
以下のコマンドを実行し現在の権限を確認しメモしておいてください。
Get-ExecutionPolicy
以下のコマンドを実行して権限を変更してください。
Set-ExecutionPolicy RemoteSigned
変更してよいか確認されるので Y キーを押してください。
なお、権限(RestrictedやRemoteSignedなどの)の詳細は以下を参照してください。
②スクリプトを作成
拡張子を表示した状態で「CorrectTimestamp.ps1」というファイルを作成しメモ帳で以下の様に編集してください。
$files = Get-ChildItem $PSScriptRoot -File -Recurse -Exclude *.json,*.html,*.ps1,*.zip
foreach($i in $files){
Write-Host $i.FullName
$jsondata = Get-Content -Path $i".json" | ConvertFrom-JSON
$j = $i.FullName+".json"
$UnixTime = $jsondata.photoTakenTime.timestamp.Trim('"')
$UtcTime = ([DateTime]::Parse("1970/01/01 00:00:00")).addSeconds($UnixTime)
$LocalTime= [TimeZoneInfo]::ConvertTimeFromUtc($UtcTime,[TimezoneInfo]::local)
Set-ItemProperty $i -name CreationTime -value $LocalTime
Set-ItemProperty $j -name CreationTime -value $LocalTime
Set-ItemProperty $i -name LastWriteTime -value $LocalTime
Set-ItemProperty $j -name LastWriteTime -value $LocalTime
}
Write-Host "Processing has finished. Please press any key. . ." -NoNewLine
[Console]::ReadKey($true) > $nullJSON内にはいくつかの日時が記録されております、上記のスクリプトではphotoLastModifiedTimeを更新日時に設定していますが、photoTakenTimeを採用するなど好みに合わせて変更してください。更新日時もphotoTakenTimeにしています。
③Zipファイルの展開とスクリプトの配置
Takeout機能でダウンロードしたZIPファイルを全て展開し、展開されたフォルダに「CorrectTimestamp.ps1」をコピーしてください。
④スクリプトの実行
「CorrectTimestamp.ps1」ファイルを右クリックして「PowerShellで実行」を行うとタイムスタンプが修正されます。
その他
JSONファイルに記録されているデータを使ってファイル名を変えたりすることもできるが、PowerShellでなくWSH(Windows Script Host)などでも処理できると思います。
2022年10月9日日曜日
バッチファイル(*.bat)でファイルをゴミ箱に入れる
バッチファイルでファイルを直接削除するのは怖いのでゴミ箱を経由する
バッチファイルで直接ゴミ箱に入れることはできないので、WSH経由で削除します。
PowerShellを使う方法もありますが、セキュリティ的に使えない事が多いのでWSHを選択しました。
また、そもそも全てをWSHで実現すれば良いと思いますが、BATファイルの方が楽なケースも多いので結構役に立ちます。
@echo on CScript.exe trash.js c:\folder\test1.txt c:\folder\test2.txt
var args = WScript.Arguments;
var sa = WScript.CreateObject("Shell.Application");
var fso = WScript.CreateObject("Scripting.FileSystemObject");
for (var i = 0; i < args.length; i++ ) {
arg = args(i);
WScript.Echo(arg);
sa.NameSpace(10).MoveHere( arg );
while( fso.FileExists( arg ) ){
WScript.Sleep(100);
}
}
// ゴミ箱の取得(NameSpace関数の10の意味)
// https://learn.microsoft.com/ja-jp/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants
// WScript.Echo( sa.NameSpace(10).Title );
今後改善したい項目
- エラー処理
- 相対パス指定対応
2022年8月28日日曜日
WSH(JScript)で*.xlsmを*.xlsxに変換する
マクロを有効にした XML ブック(*.xlsm)を既定のXML ブック(*.xlsx)に変換する。
xlsm2xlsx.jsのアイコンに変換したい *.xlsm ファイルを複数Drag&Dropすると *.xlsx ファイルを生成する。
xlsm2xlsx.js
var xlWorkbookDefault = 51;
var ExcelApp = new ActiveXObject( "Excel.Application" );
// *.jsファイルにDrag&Dropされたファイルを順番に処理
var args = WScript.Arguments;
for (var i = 0; i < args.length; i++ ) {
var orgFilename = args(i);
// 特定の拡張子のみ処理を実施
var orgExtension = orgFilename.split('.').pop();
if( orgExtension == 'xlsm' ){
var baseName = orgFilename.substring(0, orgFilename.indexOf('.'+orgExtension))
// ファイルを開いて形式を変更して保存
var book = ExcelApp.Workbooks.Open( orgFilename );
ExcelApp.DisplayAlerts= false;
book.SaveAs( baseName + '.xlsx' , xlWorkbookDefault );
ExcelApp.DisplayAlerts= true;
ExcelApp.Quit();
}
}
WScript.Echo( "finish" );
ExcelApp = null;
参考
https://docs.microsoft.com/ja-jp/office/vba/api/excel.xlfileformat
https://docs.microsoft.com/ja-jp/office/vba/api/excel.workbook.saveas
2022年7月3日日曜日
HTML+CSSでトグルボタン
すぐに忘れるので画像も文字列も変化するトグルボタン。デザインは適当。
まずはコードから
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<style>
.btn,.btn:checked+label,.btn+label+label
{
display:none;
}
.btn+label,.btn:checked+label+label
{
display:inline-block;
user-select: none;
background-repeat: no-repeat;
}
.btn+label
{
background-color:aqua;
}
.btn:checked+label+label
{
background-color:pink;
}
.btn:active+label,
.btn:active+label+label
{
background-color: lightgray;
}
.vertical+label,.vertical+label+label
{
padding-top: 2em;
background-position:top;
background-size: auto 2em;
}
.horizontal+label,.horizontal+label+label
{
padding-left: 2em;
background-position:left;
background-size: 2em auto;
}
.image+label,.image+label+label
{
height: 2em;
width: 2em;
background-position:center;
background-size: contain;
text-indent:100%;
white-space:nowrap;
overflow:hidden;
}
.text+label,.text+label+label
{
background-position:center;
background-size: 0 0;
}
</style>
<table>
<TR><TH>ボタン</TH><TH>トグル</TH></TR>
<TR><TD>
<input class="btn vertical" type="button" id="1"/>
<label for="1" style="background-image:url(send.png)">メール送信</label>
<hr>
<input class="btn horizontal" type="button" id="2"/>
<label for="2" style="background-image:url(send.png)">メール送信</label>
<hr>
<input class="btn image" type="button" id="5"/>
<label for="5" style="background-image:url(send.png)">メール送信</label>
<hr>
<input class="btn text" type="button" id="6"/>
<label for="6" style="background-image:url(send.png)">メール送信</label>
<hr>
</TD>
<TD>
<input class="btn vertical" type="checkbox" id="3"/>
<label for="3" style="background-image:url(opened.png)">既読メール</label>
<label for="3" style="background-image:url(unopened.png)">未読メール</label>
<hr>
<input class="btn horizontal" type="checkbox" id="4" checked/>
<label for="4" style="background-image:url(opened.png)">既読メール</label>
<label for="4" style="background-image:url(unopened.png)">未読メール</label>
<hr>
<input class="btn image" type="checkbox" id="7"/>
<label for="7" style="background-image:url(opened.png)">既読メール</label>
<label for="7" style="background-image:url(unopened.png)">未読メール</label>
<hr>
<input class="btn text" type="checkbox" id="8"/>
<label for="8" style="background-image:url(opened.png)">既読メール</label>
<label for="8" style="background-image:url(unopened.png)">未読メール</label>
<hr>
</TD></TR>
</table>
</body>
<html>
動作は以下のような感じ
| 普通のボタン | トグルするボタン |
|---|---|
|
|
|
2022年5月28日土曜日
GASでドライブの画像を表示する
知恵袋への回答は質問に直接答えたが、画面ロード時に時間がかかりそうなので処理を全体的に変えてみた。
function doGet(e) {
return HtmlService.createTemplateFromFile("index.html").evaluate().setTitle("タイトル");
}
function GetBase64Image(fileId,param)
{
var file = DriveApp.getFileById(fileId);
var blob = file.getBlob();
var contentType = blob.getContentType();
var base64 = Utilities.base64Encode(blob.getBytes());
var imageSrc = "data:" + contentType + ";base64, " + base64;
return {"name":file.getName(),"id":fileId,"base64":imageSrc,"param":param};
}
index.html
<html>
<head>
<script>
<?
var folder = DriveApp.getFolderById("1MdeTdaYDv9BK9QTaZes5_bTDRpKiLRnA");
var files = folder.getFiles();
var list = [];
while (files.hasNext()) {
var file = files.next();
list.push({"id":file.getId(),"name":file.getName(),"url":file.getUrl()});
}
?>
var list = <?!= JSON.stringify(list) ?>;
function onSelected(event)
{
var img = document.getElementById("image")
img.alt = "Loading....";
img.src = null;
var fileId = event.currentTarget.value;
google.script.run
.withSuccessHandler( SuccessGetBase64Image )
.withFailureHandler( Failure )
.GetBase64Image( fileId, img.id );
}
function Failure(){
alert("失敗");
}
function SuccessGetBase64Image(entry)
{
document.getElementById(entry.param).alt = entry.name;
document.getElementById(entry.param).src = entry.base64;
}
function onLoad()
{
var select = document.getElementById("select");
select.addEventListener('change', onSelected);
list.forEach( file => {
var option = document.createElement("option");
option.value = file.id;
option.innerText = file.name;
select.appendChild(option);
});
}
</script>
</head>
<body onload="onLoad();">
<label for="select">Choose a menu:</label>
<select name="select" id="select">
<option value="">--Please choose an option--</option>
</select>
<p><img src="" height="562" width="750" alt="選択されてません" align="top" id="image">ここに画像表示</p>
</script>
</body>
</html>
2022年5月14日土曜日
JavaScriptで○×ゲームを作ってみた
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<script>
let canvas;
let context;
let Magnification = 100;
let Finished = false;
let FirstStrike=true;
let MarubatsuTable={};
function drawFrame()
{
// #を描画
context.beginPath();
context.moveTo(Magnification*0, Magnification*1);
context.lineTo(Magnification*3, Magnification*1);
context.moveTo(Magnification*0, Magnification*2);
context.lineTo(Magnification*3, Magnification*2);
context.moveTo(Magnification*1, Magnification*0);
context.lineTo(Magnification*1, Magnification*3);
context.moveTo(Magnification*2, Magnification*0);
context.lineTo(Magnification*2, Magnification*3);
context.stroke();
}
function onClick(e)
{
if( Finished )
{
// ゲームが終わっている場合はリセット
context.clearRect(0, 0, canvas.width, canvas.height);
drawFrame();
MarubatsuTable = {};
Finished = false;
FirstStrike = true;
return;
}
// クリックされたセルを検出
let rect = e.target.getBoundingClientRect();
let x = Math.floor( (e.clientX - rect.left) / Magnification );
let y = Math.floor( (e.clientY - rect.top) / Magnification );
let id = "X"+ x + "Y" + y;
if( !MarubatsuTable[id] )
{
if( FirstStrike )
{
MarubatsuTable[id] = "○";
context.beginPath();
context.arc( (x+0.5)*Magnification, (y+0.5)*Magnification, Magnification/3 , 0, 2 * Math.PI, false ) ;
context.stroke();
}
else
{
MarubatsuTable[id] = "×";
context.beginPath();
context.moveTo((x+0.2)*Magnification, (y+0.2)*Magnification);
context.lineTo((x+0.8)*Magnification, (y+0.8)*Magnification);
context.moveTo((x+0.8)*Magnification, (y+0.2)*Magnification);
context.lineTo((x+0.2)*Magnification, (y+0.8)*Magnification);
context.stroke();
}
// 結果判定
const decisionTable =
[ {dicision:["X0Y0","X1Y0","X2Y0"],line:{x0:0,y0:0.5,x1:3,y1:0.5}},
{dicision:["X0Y1","X1Y1","X2Y1"],line:{x0:0,y0:1.5,x1:3,y1:1.5}},
{dicision:["X0Y2","X1Y2","X2Y2"],line:{x0:0,y0:2.5,x1:3,y1:2.5}},
{dicision:["X0Y0","X0Y1","X0Y2"],line:{x0:0.5,y0:0,x1:0.5,y1:3}},
{dicision:["X1Y0","X1Y1","X1Y2"],line:{x0:1.5,y0:0,x1:1.5,y1:3}},
{dicision:["X2Y0","X2Y1","X2Y2"],line:{x0:2.5,y0:0,x1:2.5,y1:3}},
{dicision:["X0Y0","X1Y1","X2Y2"],line:{x0:0,y0:0,x1:3,y1:3}},
{dicision:["X2Y0","X1Y1","X0Y2"],line:{x0:0,y0:3,x1:3,y1:0}} ];
decisionTable.forEach((a)=>
{
var temp = MarubatsuTable[a.dicision[0]] + MarubatsuTable[a.dicision[1]] + MarubatsuTable[a.dicision[2]];
if( temp == "○○○" || temp == "×××" )
{
context.beginPath();
context.moveTo( a.line.x0*Magnification, a.line.y0*Magnification);
context.lineTo( a.line.x1*Magnification, a.line.y1*Magnification);
context.stroke();
Finished = true;
}
if( Object.keys(MarubatsuTable).length == 9 )
{
Finished = true;
}
});
// 先攻後攻変更
FirstStrike = !FirstStrike;
}
}
function onLoad(){
canvas = document.getElementById('sampleCanvas');
context = canvas.getContext('2d');
if ( ! canvas || ! context ) {
return false;
}
canvas.style.width = Magnification * 3+"px";
canvas.style.height = Magnification * 3+"px";
canvas.width = Magnification * 3;
canvas.height = Magnification * 3;
drawFrame();
canvas.addEventListener('click', onClick, false);
}
</script>
</head>
<body onload="onLoad();">
<canvas id="sampleCanvas"></canvas>
</body>
</html>
おまけ:Canvas縛りがない場合はもっと簡単
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<script>
let FirstStrike=true;
let Finished = false;
let Count = 0;
function onClick(event){
// 勝敗決定後のクリックは結果クリア
if( Finished )
{
for (const td of event.currentTarget.querySelectorAll("td")) {
td.innerText = " ";
td.style.backgroundColor = "";
}
FirstStrike=true;
Finished = false;
Count = 0;
return;
}
// クリックされたセルに"○"/"×"を記録
if(event.target.tagName=="TD")
{
if( event.target.innerText==" " ){
event.target.innerText = FirstStrike?"○":"×";
FirstStrike =! FirstStrike;
Count++;
// 結果判定
const decisionTable =
[ ["00","01","02"],
["01","11","12"],
["02","21","22"],
["00","10","20"],
["01","11","21"],
["02","12","22"],
["00","11","22"],
["20","11","02"] ];
decisionTable.forEach((a)=>
{
var temp = document.getElementById(a[0]).innerText+document.getElementById(a[1]).innerText+document.getElementById(a[2]).innerText;
if( temp == "○○○" || temp == "×××" )
{
Finished = true;
document.getElementById(a[0]).style.backgroundColor = "lightblue";
document.getElementById(a[1]).style.backgroundColor = "lightblue";
document.getElementById(a[2]).style.backgroundColor = "lightblue";
}
});
if( Count >=9 )
{
Finished = true;
}
}
}
}
</script>
</head>
<body>
<table onclick="onClick(event);" border="1">
<tr><td id="00"> </td><td id="01"> </td><td id="02"> </td></tr>
<tr><td id="10"> </td><td id="11"> </td><td id="12"> </td></tr>
<tr><td id="20"> </td><td id="21"> </td><td id="22"> </td></tr>
</table>
</body>
</html>
2022年4月24日日曜日
Googleフォトの「ロックされたフォルダ」の使い方と注意事項
- 「ロックされたフォルダ」の概要
- 「ロックされたフォルダ」に入れるとロック解除しないと画像が閲覧できなくなります。他人に見られたくない画像などを保存する目的だと思います。
- しかし、ゴミ箱を経由せずに消えるなどいろいろと危険な使用があるので大切な画像を「ロックされたフォルダ」に入れてはいけません。
- 大切ではないけど他人には見られたくない画像を保存するのが「ロックされたフォルダ」の用途です。
- 「ロックされたフォルダ」の使い方
- 入れる方法
Googleフォトアプリで写真を表示/写真を選択し、「︙」アイコンから「ロックされたフォルダに移動」を行う。 - 参照する方法
「ロックされたフォルダ」に入れる操作を行った端末のGoogleフォトアプリで 「ライブラリ」 > 「ユーティリティ」 > 「ロックされたフォルダ」 と選んでください。ロック解除はスマホのロック解除と同じ方法です。 - 出す方法
「ロックされたフォルダ」内の画像を選択して「移動」してください。 - ロックされたフォルダに入れるとどうなるか
- 画像が端末にダウンロードされ、Googleフォトアプリの「ロックされたフォルダ」に保存される。
- 操作を行ったときに選択されていたアカウントのGoogleフォト( https://photos.google.com/login )から写真が削除される。
- ロックされたフォルダを使用するうえでの注意事項
- 「ロックされたフォルダ」内での削除操作はゴミ箱を経由しないので復元できない。
- 操作を行った端末のみに保存されるので端末故障/端末紛失/機種変更/端末初期化の際には画像がなくなる
- 操作を行ったときに選択されていたアカウントのGoogleフォト( https://photos.google.com/login )から写真が削除されるが、他のアカウントからは消えない、パートナー共有などで複数のアカウントに画像があるときはすべてのアカウントで「ロックされたフォルダ」に入れる必要がある。
- 他のアプリから参照できなくなる。
- 「ロックされたフォルダ」から出すと再度バックアップ対象になるので、2021/5/31以前にバックアップし容量無制限の対象だった画像も容量カウントの対象になる。
- 撮影日時が保存されていない画像形式(PNG形式で保存されたスクリーンショット画像)はロックされたフォルダに入れるとファイルタイムスタンプが更新されていつ撮影されたか分からなくなる。
- ロックされたフォルダの活用方法
- Pixel 5以前の端末など、節約画質で容量無制限の特典が利用できる端末で「ロックされたフォルダ」(バックアップしない設定)に入れた後に、節約画質でバックアップする設定にしてから「ロックされたフォルダ」から出すと特典が適用される。iPhoneなど対象外の端末からバックアップした画像を無制限の対象に変換できる。
- AndroidのGoogleフォトアプリには画像を一括ダウンロードする方法が提供されていないが、「ロックされたフォルダ」には一括でダウンロードできる。「ロックされたフォルダ」に入れ、すぐに出すことで一括ダウンロードが可能になるが、、、再度バックアップが行われるので注意が必要






