<!--
 ************************************************************
 *
 * Copyright (c) 2025 ysrock Co., Ltd.	<info@ysrock.co.jp>
 * Copyright (c) 2025 Yasuo Sugano	<sugano@ysrock.co.jp>
 *
 * Version	: 1.0.0
 * Update	  : 2025.03.28
 *
 ************************************************************ -->

<!--
 ******************************
 *
 * template
 *
 ****************************** -->
<template>

 <div class="notifyArea" :class="toastObj.position" v-if="notifyObj.histories.length > 0">
  <div class="historyWrap" v-for="(historyObj, historyIdx) in notifyObj.histories" :key="'history_' + historyIdx">
   <div class="header">
    <div class="time">{{ elapsedTime(historyObj.timestamp) }}</div>
    <div class="close" @click="closeNotify(historyIdx)"></div>
   </div><!-- END div.header -->
   <div class="subject" v-if="historyObj.subject" @click="clickEvent(historyObj)" :class="historyObj.onclick ? ' onclick' : ''">{{ historyObj.subject }}</div>
   <div class="body" v-if="historyObj.body" @click="clickEvent(historyObj)" :class="historyObj.onclick ? ' onclick' : ''">{{ historyObj.body }}</div>
  </div><!-- END div.historyWrap -->
 </div><!-- END div.notifyArea-->

 <div class="notifyTextArea" v-if="notifyHtml">
  <div class="notifyTextWrap">
   <div class="close" @click="notifyHtml=null"></div>
   <div class="body" v-html="notifyHtml"></div>
  </div><!-- END notifyTextWrap -->
 </div><!-- END notifyTextArea -->

 <div class="toastArea" :class="toastObj.position">
  <div class="toastWrap" v-for="(notifyObj, notifyKey) in toastObj.notifyObj" :key="'notify_' + notifyKey">
   <div class="slideWrap" :class="[(notifyObj.body ? ' isClick' : ''), (notifyObj.isHide === true ? ' hide' : '')]" :style="'border-left-color: ' + notifyObj.color">
    <div class="subject" @click="clickEvent(notifyObj)" :class="notifyObj.onclick ? ' onclick' : ''">{{ notifyObj.subject }}</div>
   </div><!-- END div.slideWrap -->
   <div class="dummyWrap">
    <div class="subject">{{ notifyObj.subject }}</div>
   </div><!-- END div.slideWrap -->
  </div><!-- END div.toastWrap -->
 </div><!-- END div.toastArea -->

</template>




<!--
 ******************************
 *
 * script
 *
 ****************************** -->
<script scoped>
export default{
  setup (props) {
    /********** イベント **********/
    const help = () => consoleLog();
    const set = (...args) => eventClosure.set(...args);
    const clear = (...args) => eventClosure.clear(...args);

    const closeNotify = (historyIdx) => notifyObj.value.histories.splice(historyIdx, 1);
    const clickEvent = (...args) => clickEventClosure.click(...args);


    /********** オブジェクト **********/
    const isLoaded = Vue.ref(true);

    /**
     * 通知履歴
     *   $notifyObj (Object)
     *     @param $isVisible (?boolean)
     *       true: 履歴窓を開く
     *       false: 履歴窓を閉じる
     *       null: 非表示
     *     @param $histories (array)
     *       履歴 (object)
     *         subject (string)
     *           件名
     *         timestamp (int)
     *           通知時刻
     *         onclick (?object)
     *           クリック時
     *             text (?string)
     *               テキスト(HTML)を表示する
     *             linkObj(?object)
     *               リンク先を開く
     *                 href (?text)
     *                   リンク先URL
     *                 target (?enum = _self)
     *                   リンクを開くウィンドウ 
     *                     '_top': ブラウザ全体
     *                     '_parent': 親フレーム
     *                     '_blank': 新しいウィンドウ
     *                     '_self': 同じフレーム
     */
    const notifyObj = Vue.ref({
      'isVisible': null,
      'histories': []
    });

    const notifyHtml = Vue.ref(null);


    /**
     * トースト表示
     *   $toastObj (Object)
     *     position (?enum = rightBottom)
     *       'rightBottom', 'rightTop'
     *     displayTime (?int = 5000)
     *       表示時間（ミリセカンド）
     *     notifyObj (Object)
     *       key (int)
     *       value (Object)
     *         subject (string)
     *           件名
     *         color (?string = '#00e')
     *           通知バーの色
     *         onclick (?object)
     *           クリック時
     *             text (?string)
     *               テキスト(HTML)を表示する
     *             linkObj(?object)
     *               リンク先を開く
     *                 href (?text)
     *                   リンク先URL
     *                 target (?enum = _self)
     *                   リンクを開くウィンドウ 
     *                     '_top': ブラウザ全体
     *                     '_parent': 親フレーム
     *                     '_blank': 新しいウィンドウ
     *                     '_self': 同じフレーム
     */
    const toastObj = Vue.ref({
      'position': 'rightBottom',
      'displayTime': 5000,
      'notifyObj': {}
    });
    


    /**
     * ヘルプを表示
     */
    const consoleLog = () => {
      let text = "";
      text += " ************************************************************\n";
      text += " *\n";
      text += " *  Copyright (c) 2025 ysrock Co., Ltd.	<info@ysrock.co.jp>\n";
      text += " *  Copyright (c) 2025 Yasuo Sugano	<sugano@ysrock.co.jp>\n";
      text += " *\n";
      text += " *  Version : 1.0.0\n";
      text += " *  Date    : 2025.03.28\n";
      text += " *\n";
      text += " *    ローディングを表示\n";
      text += " *\n";
      text += " ************************************************************\n";
      text += " *\n";
      text += " *  通知を表示\n";
      text += " *    REFERENCE.value.set(string $subject, string $color, object $onclick)\n";
      text += " *\n";
      text += " *      @param $subject (string)\n";
      text += " *        表示する通知\n";
      text += " *\n";
      text += " *      @param $color (?string = '#00e')\n";
      text += " *        通知バーの色\n";
      text += " *\n";
      text += " *      @param $onclick (?object)\n";
      text += " *        クリック時\n";
      text += " *\n";
      text += " *        @param $text (?string)\n";
      text += " *          テキスト(HTML)を表示する\n";
      text += " *\n";
      text += " *        @param $link (?object)\n";
      text += " *          リンク先を開く\n";
      text += " *\n";
      text += " *          @param $href (?string)\n";
      text += " *            リンク先URL\n";
      text += " *\n";
      text += " *          @param $target (?enum = _self)\n";
      text += " *            リンクを開くウィンドウ \n";
      text += " *              '_top': ブラウザ全体\n";
      text += " *              '_parent': 親フレーム\n";
      text += " *              '_blank': 新しいウィンドウ\n";
      text += " *              '_self': 同じフレーム\n";
      text += " *\n";
      text += " ************************************************************\n";
      text += " *\n";
      text += " *  全通知をクリア\n";
      text += " *    REFERENCE.value.clear()\n";
      text += " *\n";
      text += " ************************************************************\n";
      console.debug(text);
    };
    consoleLog();


    /**
     * イベント
     */
    const eventClosure = (() => {
      return {
        /**
         * 通知を表示
         */
        set: function (subject, color, onclickObj) {
          if (color == undefined || color == null) color = "";
          if (onclickObj == undefined) onclickObj = {};
          if (onclickObj.text === null && onclickObj.linkObj.href === null) onclickObj = null;
          const body = this.setBody(onclickObj);
          const ms = Date.now();
          this.setNotify(subject, body, onclickObj);
          this.setToast(subject, body, onclickObj, color, ms);
          this.setToastShowTimer(ms);
          this.setToastHideTimer(ms);
        },
        setBody: (onclickObj) => {
          if (!onclickObj) return;
          let body = "";
          if ('text' in onclickObj && onclickObj.text) body = onclickObj.text.replace(/<[^\x3e]*>/g, '');
          else if ('linkObj' in onclickObj && 'href' in onclickObj.linkObj && onclickObj.linkObj.href) body = onclickObj.linkObj.href;
          return body.substring(0, 20);
        },

        // 通知に追加
        setNotify: (subject, body, onclickObj) => {
          let insObj = {
            'subject': subject,
            'body': body,
            'timestamp': Math.floor(Date.now()/1000),
            'onclick': onclickObj
          };
          notifyObj.value.histories.push(insObj);
        },
        // トーストに追加
        setToast: function (subject, body, onclickObj, color, ms) {
          let insObj = {
            'subject': subject,
            'body': body,
            'color': color ? color : '#00e',
            'isHide': true,
            'onclick': onclickObj
          };
          toastObj.value.notifyObj[ms] = insObj;
        },
        // setToastBody: (onclickObj) => {
        //   let body = "";
        //   if ('text' in onclickObj && onclickObj.text) body = onclickObj.text.replace(/<[^\x3e]*>/g, '');
        //   else if ('linkObj' in onclickObj && 'href' in onclickObj.linkObj && onclickObj.linkObj.href) body = onclickObj.linkObj.href;
        //   return body.substring(0, 20);
        // },
        setToastShowTimer: (ms) => {
          setTimeout(() => toastObj.value.notifyObj[ms].isHide = false, 100);
        },
        setToastHideTimer: function (ms) {
          setTimeout(() => toastObj.value.notifyObj[ms].isHide = true, toastObj.value.displayTime);
          setTimeout(() => delete toastObj.value.notifyObj[ms], toastObj.value.displayTime + 1000);
        },


        /**
         * 全通知をクリア
         */
        clear: () => {
          notifyObj.value.histories = [];
          // toastObj.value.notifyObj = {};
        },
      };
    })();


    const elapsedTime = (historyTs) => {
      const nowTs = Math.floor(Date.now()/1000);
      const diffTs = nowTs - historyTs;
      if (diffTs < 60) return diffTs + "秒前";
      if (diffTs < 3600) return Math.floor(diffTs/60) + "分前";
      if (diffTs < 86400) return Math.floor(diffTs/3600) + "時間前";
      return Math.floor(diffTs/86400) + "日前";
    };


    const clickEventClosure = (() => {
      return {
        click: function (obj) {
          if (!obj.onclick) return;
          if ('text' in obj.onclick && obj.onclick.text) {
            return notifyHtml.value = obj.onclick.text ? "<strong>" + obj.subject + "</strong><br>" + obj.onclick.text.replace(/\r\n|\r|\n/g, '<br>') : null;
          } else if ('linkObj' in obj.onclick && 'href' in obj.onclick.linkObj && obj.onclick.linkObj.href) {
            return window.open(obj.onclick.linkObj.href, obj.onclick.linkObj.target)
          };
        },
      };
    })();


    return {
      help,
      set,
      clear,
      closeNotify,
      clickEvent,

      isLoaded,
      notifyObj,
      notifyHtml,
      toastObj,

      elapsedTime,
    };

  }
}
</script>


<!--
 ******************************
 *
 * style
 *
 ****************************** -->
<style scoped>
/**
 * 通知の履歴
 */
div.notifyArea * {
  font-size: 1em;
  line-height: 1em;
}

div.notifyArea {
  position: fixed;
  z-index: 10001;
  height: 100vh;
  padding: 0 .1em;
  background-color: #fafafa;
  box-shadow: 0 0 .5em 0 #888;
  overflow-x: hidden;
  overflow-y: auto;
}
div.notifyArea.rightTop {
  top: 0;
  transition: right .2s;
}
div.notifyArea.rightBottom {
  bottom: 0;
  display: flex;
  flex-direction: column-reverse;
  transition: right .2s;
}
div.notifyArea.rightTop:hover,
div.notifyArea.rightBottom:hover {
  right: 0;
}
@media print, screen and (min-width: 1025px) {
  div.notifyArea {
    width: 250px;
    max-width: 250px;
  }
  div.notifyArea.rightTop,
  div.notifyArea.rightBottom {
    right: calc(-250px + 1em);
  }
}
@media screen and (min-width: 641px) and (max-width: 1024px) {
  div.notifyArea {
    width: 29.274vw;
    max-width: 29.274vw;
  }
}
@media screen and (max-width: 640px) {
  div.notifyArea {
    width: 62.5vw;
    max-width: 62.5vw;
  }
}
div.notifyArea > div.historyWrap {
  margin: 1em;
  padding: .5em 0;
  background-color: #fff;
  box-shadow: .2em .2em .3em 0 #aaa;
  white-space: nowrap;
}
div.notifyArea > div.historyWrap > div.header {
  display: flex;
  padding: 0 1em;
}
div.notifyArea > div.historyWrap > div.header > div.time {
  flex: 1;
  position: relative;
  top: .2em;
  padding-right: 1em;
  font-size: .8em;
  text-align: right;
}
div.notifyArea > div.historyWrap > div.header > div.close {
  position: relative;
  width: 1em;
  height: 1em;
  cursor: pointer;
}
div.notifyArea > div.historyWrap > div.header > div.close::before,
div.notifyArea > div.historyWrap > div.header > div.close::after {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  content: "";
  width: 1em;
  height: .2em;
  margin: auto;
  background-color: #aaa;
}
div.notifyArea > div.historyWrap > div.header > div.close::before {
  transform: rotate(45deg);
}
div.notifyArea > div.historyWrap > div.header > div.close::after {
  transform: rotate(-45deg);
}
div.notifyArea > div.historyWrap > div.subject {
  padding: .8em .8em 0 .8em;
  font-size: .9em;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
  cursor: default;
}
div.notifyArea > div.historyWrap > div.subject.onclick {
  cursor: pointer;
}
div.notifyArea > div.historyWrap > div.body {
  padding: .8em;
  font-size: .8em;
  color: #888;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
  cursor: default;
}
div.notifyArea > div.historyWrap > div.body.onclick {
  cursor: pointer;
}

/**
 * 通知テキスト
 */
div.notifyTextArea {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 10002;
  width: 100vw;
  height: 100vh;
  background-color: rgba( 0, 0, 0, .2);
}
div.notifyTextArea > div.notifyTextWrap {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  min-width: 25vw;
  max-width: 90vw;
  min-height: 10vh;
  max-height: 90vh;
  background-color: white;
  border-radius: .4em;
  box-shadow: .2em .2em .4em 0 rgba(0, 0, 0, .4);
  overflow: hidden auto;
}
div.notifyTextArea > div.notifyTextWrap > div.close {
  position: relative;
  display: inline-block;
  width: 2.6em;
  height: 2.2em;
  margin-left: auto;
  cursor: pointer;
}
div.notifyTextArea > div.notifyTextWrap > div.close::before,
div.notifyTextArea > div.notifyTextWrap > div.close::after {
  content: "";
  position: absolute;
  top: 1em;
  right: .6em;
  width: 1.4em;
  height: .2em;
  background-color: #aaa;
}
div.notifyTextArea > div.notifyTextWrap > div.close::before {
  transform: rotate(45deg);
}
div.notifyTextArea > div.notifyTextWrap > div.close::after {
  transform: rotate(-45deg);
}
div.notifyTextArea > div.notifyTextWrap > div.body {
  flex: 1;
  padding: 0 1em 1em;
}


/**
 * トースト
 */
div.toastArea * {
  font-size: 1em;
  line-height: 1em;
}
div.toastArea.rightTop {
  position: fixed;
  z-index: 10000;
  top: 1em;
  right: 2em;
}
div.toastArea.rightBottom {
  position: fixed;
  z-index: 10000;
  bottom: 1em;
  right: 2em;
}
div.toastArea > div.toastWrap {
  position: relative;
  padding: .5em 0;
  white-space: nowrap;
}
div.toastArea > div.toastWrap > div.slideWrap {
  position: absolute;
  right: 0%;
  transition: right .2s;
  border-left-width: .4em;
  border-left-style: solid;
  background-color: white;
  box-shadow: .25em .25em .75em 0 rgba(0, 0, 0, .2);
  cursor: default;
}
div.toastArea > div.toastWrap > div.slideWrap.hide {
  right: calc(-100% - 3em);
}
div.toastArea > div.toastWrap > div.slideWrap.isClick {
  cursor: pointer;
}
div.toastArea > div.toastWrap > div.slideWrap > div.subject,
div.toastArea > div.toastWrap > div.dummyWrap > div.subject {
  max-width: 80vw;
  padding: .5em .6em .5em .7em;
  font-weight: bold;
  letter-spacing: .05em;
  color: #555;
  text-overflow: ellipsis;
  overflow: hidden;
}
div.toastArea > div.toastWrap > div.dummyWrap {
  visibility: hidden;
}

</style>