@@ -0,0 +1,44 @@ | |||||
export default class | |||||
CommonModule | |||||
{ | |||||
static | |||||
isWide (chr) | |||||
{ | |||||
return chr.match (/[^\x01-\x7f]/) !== null; | |||||
} | |||||
static | |||||
lenByFull (str) | |||||
{ | |||||
return str.split ('').map (c => this.isWide (c) ? 1 : .5) | |||||
.reduce ((a, c) => a + c); | |||||
} | |||||
static | |||||
indexByFToC (str, index) | |||||
{ | |||||
let i = 0; | |||||
let work = ''; | |||||
for (let c of str) | |||||
{ | |||||
work += c; | |||||
if (this.lenByFull (work) > index) | |||||
break; | |||||
i += 1; | |||||
} | |||||
return i; | |||||
} | |||||
static | |||||
midByFull (str, start, length) | |||||
{ | |||||
const trimmedLeft = str.slice (this.indexByFToC (str, start)); | |||||
return trimmedLeft.slice (0, this.indexByFToC (trimmedLeft, length)); | |||||
} | |||||
} | |||||
@@ -22,12 +22,18 @@ | |||||
</head> | </head> | ||||
<body> | <body> | ||||
<?php if (!($available)): ?> | |||||
<div class="alert alert-danger" role="alert"> | |||||
<strong>【警報】</strong>AIニジカが落ちてるぬ゛〜゛ん゛(泣) | |||||
</div> | |||||
<?php endif ?> | |||||
<div class="container my-5"> | <div class="container my-5"> | ||||
<div class="accordion mb-5" id="accordion-filter"> | <div class="accordion mb-5" id="accordion-filter"> | ||||
<div class="accordion-item"> | <div class="accordion-item"> | ||||
<h2 class="accordion-header"> | <h2 class="accordion-header"> | ||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-filter" aria-controls="collapse-filter"> | <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-filter" aria-controls="collapse-filter"> | ||||
絞り込みてすと | |||||
絞り込み | |||||
</button> | </button> | ||||
</h2> | </h2> | ||||
@@ -124,9 +130,9 @@ | |||||
$length, | $length, | ||||
true) | true) | ||||
as $record): ?> | as $record): ?> | ||||
<div class="mb-4"> | |||||
<div class="mb-4 message-block"> | |||||
<div> | <div> | ||||
<?= $record['date_time'] ?> | |||||
<span class="message-block-dt"><?= $record['date_time'] ?></span> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
@@ -135,11 +141,11 @@ | |||||
</div> | </div> | ||||
<div style="color: blue"> | <div style="color: blue"> | ||||
> <span style="font-style: italic"><?= $record['chat_message'] ?></span> | |||||
> <span class="message-block-chat" style="font-style: italic"><?= $record['chat_message'] ?></span> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<?= $record['answer'] ?> | |||||
<span class="message-block-answer"><?= $record['answer'] ?></span> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<?php endforeach ?> | <?php endforeach ?> | ||||
@@ -7,6 +7,11 @@ const LOG_PATH = './log.txt'; | |||||
$log_data = []; | $log_data = []; | ||||
exec ("(ps -Af | grep -e '^miteruzo' | grep 'python3 main.py') && (ps -Af | grep -e '^miteruzo' | grep 'obs')", | |||||
$output, $exit_code); | |||||
$available = $exit_code === 0; | |||||
unset ($output, $exit_code); | |||||
$page = (int) ($_GET['p'] ?? 1); | $page = (int) ($_GET['p'] ?? 1); | ||||
$length = (int) ($_GET['max'] ?? 20); | $length = (int) ($_GET['max'] ?? 20); | ||||
$asc = ($_GET['asc'] ?? 0) != 0; | $asc = ($_GET['asc'] ?? 0) != 0; | ||||
@@ -78,6 +78,15 @@ Script | |||||
$.cookie ('expand-filter', '0'); | $.cookie ('expand-filter', '0'); | ||||
}); | }); | ||||
$ ('.message-block').on ('click', function () | |||||
{ | |||||
const dt = $ (this).find ('.message-block-dt').text (); | |||||
const chat = $ (this).find ('.message-block-chat').text (); | |||||
const answer = $ (this).find ('.message-block-answer').text (); | |||||
window.open (`./talk.php?dt=${dt}&chat=${chat}&answer=${answer}`); | |||||
}); | |||||
if ($.cookie ('expand-filter') === '0') | if ($.cookie ('expand-filter') === '0') | ||||
{ | { | ||||
$ ('#collapse-filter').removeClass ('show'); | $ ('#collapse-filter').removeClass ('show'); | ||||
@@ -0,0 +1,32 @@ | |||||
/* vim:set tabstop=2 softtabstop=2 expandtab :*/ | |||||
@font-face | |||||
{ | |||||
font-family: 'Nikumaru'; | |||||
src: url(./assets/nikumaru.otf); | |||||
} | |||||
html, body | |||||
{ | |||||
overflow: hidden; | |||||
} | |||||
body | |||||
{ | |||||
background-color: black; | |||||
} | |||||
#canvas | |||||
{ | |||||
width: 100%; | |||||
height: 100%; | |||||
padding: 0; | |||||
margin: 0; | |||||
overflow: unset; | |||||
border: none | |||||
!important; | |||||
outline: none | |||||
!important; | |||||
display: block; | |||||
} | |||||
@@ -0,0 +1,23 @@ | |||||
<!-- vim:set tabstop=4 softtabstop=4 expandtab :--> | |||||
<!DOCTYPE html> | |||||
<html lang="ja"> | |||||
<head> | |||||
<meta charset="UTF-8" /> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||||
<script src="//code.jquery.com/jquery-3.3.1.min.js"></script> | |||||
<link rel="stylesheet" href="./talk.css?<?= filemtime ('./talk.css') ?>" /> | |||||
<link rel="preconnect" href="./assets/nikumaru.otf" /> | |||||
<title>あほ</title> | |||||
<input type="hidden" id="dt" value="<?= $dt ?>" /> | |||||
<input type="hidden" id="chat" value="<?= $chat ?>" /> | |||||
<input type="hidden" id="answer" value="<?= $answer ?>" /> | |||||
</head> | |||||
<body> | |||||
<canvas id="canvas"></canvas> | |||||
<script src="./talk.js?<?= filemtime ('./talk.js') ?>" type="module"></script> | |||||
</body> | |||||
</html> | |||||
@@ -0,0 +1,136 @@ | |||||
import CommonModule from './common_module.js'; | |||||
class | |||||
Talk | |||||
{ | |||||
static | |||||
main () | |||||
{ | |||||
const canvas = new Canvas; | |||||
window.onresize = () => canvas.resize (); | |||||
} | |||||
} | |||||
class | |||||
Canvas | |||||
{ | |||||
constructor () | |||||
{ | |||||
this.canvas = $ ('#canvas'); | |||||
this.ctx = this.canvas[0].getContext ('2d'); | |||||
(this.bg = new Image ()).src = './assets/bg.jpg'; | |||||
this.bg.onload = () => this.resize (); | |||||
(this.nizika = new Image ()).src = './assets/nizika.png'; | |||||
this.nizika.onload = () => this.resize (); | |||||
(this.talking = new Image ()).src = './assets/talking.png'; | |||||
this.talking.onload = () => this.resize (); | |||||
(new FontFace ('Nikumaru', 'url(./assets/nikumaru.otf)')).load ().then ( | |||||
() => this.resize ()); | |||||
} | |||||
resize () | |||||
{ | |||||
this.canvas[0].width = $ (window).width (); | |||||
this.canvas[0].height = $ (window).height (); | |||||
this.redraw (); | |||||
} | |||||
redraw () | |||||
{ | |||||
this.putBG (); | |||||
this.putText (0, 0, 15, $ ('#dt').val ()); | |||||
this.putImage (this.nizika, 370, 260, 1.1); | |||||
this.putImage (this.talking, 0, 0, 640 / 1024); | |||||
this.putText (75, 43.75, 20, | |||||
('> ' + ((CommonModule.lenByFull ($ ('#chat').val ()) <= 21) | |||||
? $ ('#chat').val () | |||||
: (CommonModule.midByFull ($ ('#chat').val (), 0, 19.5) | |||||
+ '...'))), | |||||
undefined, undefined, | |||||
true); | |||||
this.putText (62.5, 93.75, 31.25, | |||||
((CommonModule.lenByFull ($ ('#answer').val ()) <= 16) | |||||
? $ ('#answer').val () | |||||
: CommonModule.midByFull ($ ('#answer').val (), 0, 16)), | |||||
'Nikumaru', '#c00000'); | |||||
if (CommonModule.lenByFull ($ ('#answer').val ()) > 16) | |||||
{ | |||||
this.putText (62.5, 125, 31.25, | |||||
((CommonModule.lenByFull ($ ('#answer').val ()) <= 32) | |||||
? CommonModule.midByFull ($ ('#answer').val (), 16, 16) | |||||
: (CommonModule.midByFull ($ ('#answer').val (), 16, 14.5) | |||||
+ '...')), | |||||
'Nikumaru', '#c00000'); | |||||
} | |||||
} | |||||
putBG () | |||||
{ | |||||
const [x, y, zoom] = this.convertPosition (0, 0); | |||||
const width = this.bg.height * 4 / 3; | |||||
const height = this.bg.height; | |||||
this.ctx.drawImage (this.bg, | |||||
(852 - 640) / 2, 0, width, height, | |||||
x, y, width * zoom, height * zoom); | |||||
} | |||||
putImage (image, x, y, zoom = 1) | |||||
{ | |||||
let zoom2 | |||||
[x, y, zoom2] = this.convertPosition (x, y); | |||||
zoom *= zoom2 | |||||
this.ctx.drawImage (image, | |||||
0, 0, image.width, image.height, | |||||
x, y, image.width * zoom, image.height * zoom); | |||||
} | |||||
putText (x, y, size, text, style = 'sans-serif', colour = 'black', | |||||
italic = false) | |||||
{ | |||||
let zoom; | |||||
[x, y, zoom] = this.convertPosition (x, y); | |||||
size *= zoom; | |||||
this.ctx.font = `${italic ? 'italic ' : ''}${Math.trunc (size)}px ${style}`; | |||||
this.ctx.fillStyle = colour; | |||||
this.ctx.textBaseline = 'top'; | |||||
this.ctx.fillText (text, x, y); | |||||
} | |||||
convertPosition (x, y) | |||||
{ | |||||
const sizeX = this.canvas.width (); | |||||
const sizeY = this.canvas.height (); | |||||
const width = this.bg.height * 4 / 3; | |||||
const height = this.bg.height; | |||||
const vertical = sizeY / sizeX > height / width; | |||||
const zoom = vertical ? (sizeX / width) : (sizeY / height); | |||||
const baseX = vertical ? 0 : ((sizeX - width * zoom) / 2); | |||||
const baseY = vertical ? ((sizeY - height * zoom) / 2) : 0; | |||||
return [baseX + x * zoom, baseY + y * zoom, zoom]; | |||||
} | |||||
} | |||||
$ (() => Talk.main ()); | |||||
@@ -0,0 +1,11 @@ | |||||
<?php | |||||
if ($_SERVER['HTTP_HOST'] === 'nizika.monster') | |||||
header ('location: //nizika.tv/talk.php'); | |||||
$dt = htmlspecialchars ($_GET['dt']); | |||||
$chat = htmlspecialchars ($_GET['chat']); | |||||
$answer = htmlspecialchars ($_GET['answer']); | |||||
require_once './talk.frm.php'; | |||||