Google Apps Script html Javascript Programing

Made a quiz app with Google Drive + GAS to guess the name of the image.

Quiz just to guess the file name of the image

As one gets older, one's memory becomes more and more suspect.

It often happens that a picture pops into my head, but the name doesn't come up.

To combat aging as much as possible, I created an image quiz application.

Put the image file you want to make a question in any Google Drive folder.

The name of the image file minus the extension is the answer to the quiz.

For example, name the image of Tokugawa Ieyasu something like "Tokugawa Ieyasu.png".

The folder should be accessible to anyone who knows the URL in the "Manage Access" setting.

So it is recommended to make sure that the image is safe for anyone to see.

The application is a Google Apps Script (GAS) web application.

When the images are ready, access the URL of the web application.

Then, a randomly selected image from the folder will be displayed.

Click on the image to see the file name = answer.

Click again to see the next image. Repeat this process to play.

This is a very simple quiz application, but you can easily create questions by simply naming the image file and saving it to Google Drive.

You can make questions about people's names, guessing place names from a map, animal names, or anything else that associates text with an image.

You might want to add an answer and grading function, but I don't think that an app for self-study, where you also create your own questions, needs an answer and grading function.

Folder ID required to access Google Drive

The script is a spreadsheet bind script.

To access a Google Drive folder from GAS, a folder ID is required.

The folder ID is the string after the forlders/ in the URL when you open the Google Drive folder in your browser.

https://drive.google.com/drive/u/0/folders/******* FOLDER ID *******

Put this ID in cell B1 of the spreadsheet. Just rewrite here to change the problem to an image in a different folder.

In my case, I have written the ID of the other folder in the empty cell.

With a little more code, you can add a menu to the web app that references the information in the spreadsheet to select the question.

I referred to the following article about retrieving files and displaying images in Google Drive. Big thanks.

Qiita:GASで動的HTML② HTML内でGoogleドライブ内の画像ファイルをダイナミックに表示させる。

https://qiita.com/jooji/items/b89ce338af359742e813

Hosting html in GAS

The application was built in GAS hosting html + javascript + css.

The basic methodology can be found in the following article.

The code is code.js, index.html, script.html, and style.html.

The extension of script and css is also html, which is a promise when hosting in GAS.

The actual code is left below.

code.js

const sheet = SpreadsheetApp.getActiveSheet();
const FOLDER_ID = sheet.getRange("B1").getValue();

function doGet() {
  htmlOutput = HtmlService.createTemplateFromFile("index.html").evaluate();
  htmlOutput.setTitle("Image Quiz - what/who is this? -");
  return htmlOutput;
}

function getFileListInFolder() {
  const folder = DriveApp.getFolderById(FOLDER_ID),
    files = folder.getFiles(),
    list = [];

  while (files.hasNext()) {
    const fileNext = files.next();
    const fileID = fileNext.getId();
    const fileName = String(fileNext);
    const extension = getExtension(fileName);
    if (!isPictureFile(extension)) continue;
    list.push({ fileID: fileID, fileName: removeExtension(String(fileNext)) });

  };
  return list;

}

function removeExtension(filename) {
  var lastDotPosition = filename.lastIndexOf(".");
  if (lastDotPosition === -1) return filename;
  else return filename.substr(0, lastDotPosition);
}

function getExtension(filename) {
  return filename.split(".").pop();
}

function isPictureFile(filename) {
  const pictureExtensions = ["jpg", "JPG", "jpeg", "JPEG", "png", "PNG"];
  const ext = filename.split(".").pop();
  return pictureExtensions.includes(ext);
}

index.html

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Picture Quiz</title>

  <base target="_top">
  <?!=HtmlService.createHtmlOutputFromFile('style').getContent();?>
</head>

<body>
  <div id="wrapper">
    <p id="doc">What/Who is this ? Click on Picture to show answer or go next</p>
    <p id="caption"></p>
    <img id="image" src="">
  </div>


  <?!=HtmlService.createHtmlOutputFromFile('script').getContent(); ?>
</body>

</html>

script.html

I have created a class for quizzes to simplify the process of data retention, question shuffling, and question submission without overlap.

This class is rather versatile, so I will write it down separately.

I use google.script.run to call the function to access Google Drive in code.js.

When I used DriveApp.getFolderById() in script.html, I got an error. Putting the Drive API in the library did not solve the problem.

<script>
  window.onload=function(){

let quiz={};

google.script.run.withSuccessHandler(setUp)
          .getFileListInFolder();


function setUp(list){
  quiz = new quenstionsClass(list);
  quiz.shuffle();
  const caption = document.getElementById("caption");
	caption.style.visibility ="hidden";
  const nextImage = document.createElement("img");
  const image = document.getElementById("image");

  image.addEventListener("click", function () {
	if(caption.style.visibility=="hidden"){
		// hiddenで非表示
		caption.style.visibility ="visible";
    return;
	}else{
		// visibleで表示
		caption.style.visibility ="hidden";
	}

    image.src =  nextImage.src;
    question = quiz.get;
    caption.innerText = question.fileName;
    nextImage.src = "https://drive.google.com/uc?id=" + quiz.whatsNext.fileID;// preload for faster action
  });

  let question = quiz.get;
  let next = quiz.whatsNext;
  image.src = "https://drive.google.com/uc?id=" + question.fileID;
  nextImage.src = "https://drive.google.com/uc?id=" + next.fileID;
  caption.innerText = question.fileName;

}


class quenstionsClass {
  constructor(_q_array) {
    this._data = _q_array;
    this._counter = 0;
    this._index = Array.from({ length: _q_array.length }, (a, idx) => idx);
    this._lastQuestion = {};
    this._lastCounter = 0;
    // this.shuffle();
  }

  get random() {
    //ランダムにデータをひとつ選んで返す。カウンターは無関係。同じモノが続く場合もある
    const i = Math.floor(Math.random() * this._data.length);
    return this._data[i];
  }

  get data() {
    return this._data;
  }

  get index(){
    return this._lastCounter;
  }

  get isLastQuestion(){
    return this._lastCounter === ( index.length - 1 );
  }

  shuffle() {
    if (this._index.length === 1) return;
    this._lastQuestion = this._index[this._counter];
    for (let _i = this._index.length - 1; _i > 0; _i--) {
      let _j = Math.floor(Math.random() * (_i + 1)); 
      [this._index[_i], this._index[_j]] = [this._index[_j], this._index[_i]];
    }
    if (this._lastQuestion == this._data[this._index[0]]) [this._index[0], this._index[1]] = [this._index[1], this._index[0]];
    this._counter = 0;
  }

  reset() {
    let i = this._index.length;
    while (i--) this._index[i] = i;
    this._counter = 0;
  }

  get get() {
    this._lastQuestion = this._data[this._index[this._counter]];
    this._lastCounter=this._counter;
    if (this._counter === (this._data.length - 1)) {
      // this.shuffle();
      this._counter = 0;
    } else {
      this._counter++;
    }
    return this._lastQuestion;
  }

  get whatsNext() {
    return this._data[this._index[this._counter]];
  }

}

}

</script>

style.html

<style>
  img {
    object-fit: contain;
    width: auto;
    height: auto;
    width: 90%;
    max-height: 90vh;

  }

  #caption {
    text-align: center;
    font-size: xx-large;
    font-weight: bolder;
    margin: 0em;
  }

  #doc{
    text-align: center;
    margin: 0em;


  }
</style>

It didn't work on anything but the PC version of Chrome.

It takes a little while for the first image to appear, but I thought I had created a pretty nice looking application.

But when I tried accessing it on my iPhone or iPad, the images wouldn't show up.

It seems to work only with Chrome for PC.

It seems that Google Drive's image display is different from the processing of ordinary links, so it seems to be stuck in that area.

I gave up, but there is more to this story…

PS: I asked ChatGPT and they half solved the problem.

I then asked ChatGPT why the images could not be displayed when accessed from a mobile device.

They gave me a brilliant answer.

There are two ways to specify images in Google Drive with img tags.

I had chosen the one without thinking about it, but it seems that there was a difference in mobile browser support depending on the writing style.

Actually, the code I have posted has already been modified.

When I accessed the site from Safari on my iPhone/iPad, the images were displayed properly. However, the iPhone/iPad version of Chrome did not.

-Google Apps Script, html, Javascript, Programing