Add image thumbnail cache, do lots of adjustments
This commit is contained in:
parent
ac1e937dfd
commit
0489caabca
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ settings.php
|
|||||||
nbproject/private
|
nbproject/private
|
||||||
*.sync-conflict*
|
*.sync-conflict*
|
||||||
/database.mwb.bak
|
/database.mwb.bak
|
||||||
|
/cache/thumb/*.jpg
|
11
cache/.htaccess
vendored
Normal file
11
cache/.htaccess
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
# Redirect 404'd thumbnail requests to a script that will generate them.
|
||||||
|
|
||||||
|
RewriteEngine on
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^(.*)/?$ handle_404.php?file=$1 [QSA,L]
|
31
cache/handle_404.php
vendored
Normal file
31
cache/handle_404.php
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . "/../required.php";
|
||||||
|
|
||||||
|
$fileparts = explode(".", end(explode("/", $_GET['file'])));
|
||||||
|
|
||||||
|
if (count($fileparts) != 3 || !preg_match("/[0-9]+/", $fileparts[1]) || $fileparts[2] != "jpg") {
|
||||||
|
http_response_code(403);
|
||||||
|
die("Invalid filename.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match("/^[A-Za-z0-9\-!_]+$/", $fileparts[0])) {
|
||||||
|
http_response_code(403);
|
||||||
|
die("Encoded image URL invalid, refusing to parse.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = Base64::decode($fileparts[0]);
|
||||||
|
|
||||||
|
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||||
|
http_response_code(403);
|
||||||
|
die("Invalid URL.");
|
||||||
|
}
|
||||||
|
|
||||||
|
//header("Content-Type: image/jpeg");
|
||||||
|
echo Thumbnail::addThumbnailToCache($url, (int) $fileparts[1]);
|
3
cache/thumb/README.md
vendored
Normal file
3
cache/thumb/README.md
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This folder is for the thumbnail cache.
|
||||||
|
|
||||||
|
This file is so Git doesn't remove the folder.
|
@ -5,5 +5,6 @@
|
|||||||
"health": "Health",
|
"health": "Health",
|
||||||
"science": "Science",
|
"science": "Science",
|
||||||
"sports": "Sports",
|
"sports": "Sports",
|
||||||
"technology": "Technology"
|
"technology": "Technology",
|
||||||
|
"social": "Social"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Home": "Home",
|
"Overview": "Overview",
|
||||||
"Form": "Form"
|
"News": "News",
|
||||||
|
"Headlines": "Headlines"
|
||||||
}
|
}
|
||||||
|
53
lib/Base64.lib.php
Normal file
53
lib/Base64.lib.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative Base64 encoding/decoding that's safe for filenames and URLs.
|
||||||
|
*/
|
||||||
|
class Base64 {
|
||||||
|
|
||||||
|
const STANDARD_CHARS = ['+', '/', '='];
|
||||||
|
const ALTERNATE_CHARS = ['-', '!', '_'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode $data into "alternate" Base64.
|
||||||
|
* @param mixed $data
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function encode($data): string {
|
||||||
|
return self::toAlternate(base64_encode($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode "alternate" Base64 into the original data.
|
||||||
|
* @param string $base64
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function decode(string $base64) {
|
||||||
|
return base64_decode(self::toStandard($base64));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert "alternate" Base64 into standard Base64.
|
||||||
|
* @param string $base64
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function toStandard(string $base64): string {
|
||||||
|
return str_replace(self::ALTERNATE_CHARS, self::STANDARD_CHARS, $base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert standard Base64 into URL and filename safe "alternate" Base64.
|
||||||
|
* @param string $base64
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function toAlternate(string $base64): string {
|
||||||
|
return str_replace(self::STANDARD_CHARS, self::ALTERNATE_CHARS, $base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,6 +18,7 @@ class NewsCategory {
|
|||||||
const SCIENCE = 16;
|
const SCIENCE = 16;
|
||||||
const SPORTS = 32;
|
const SPORTS = 32;
|
||||||
const TECHNOLOGY = 64;
|
const TECHNOLOGY = 64;
|
||||||
|
const SOCIAL = 128;
|
||||||
|
|
||||||
const CATEGORIES = [
|
const CATEGORIES = [
|
||||||
self::BUSINESS => "Business",
|
self::BUSINESS => "Business",
|
||||||
@ -26,7 +27,8 @@ class NewsCategory {
|
|||||||
self::HEALTH => "Health",
|
self::HEALTH => "Health",
|
||||||
self::SCIENCE => "Science",
|
self::SCIENCE => "Science",
|
||||||
self::SPORTS => "Sports",
|
self::SPORTS => "Sports",
|
||||||
self::TECHNOLOGY => "Technology"
|
self::TECHNOLOGY => "Technology",
|
||||||
|
self::SOCIAL => "Social"
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(int $category) {
|
public function __construct(int $category) {
|
||||||
@ -61,6 +63,8 @@ class NewsCategory {
|
|||||||
return "sports";
|
return "sports";
|
||||||
case self::TECHNOLOGY:
|
case self::TECHNOLOGY:
|
||||||
return "technology";
|
return "technology";
|
||||||
|
case self::SOCIAL:
|
||||||
|
return "social";
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -91,6 +95,9 @@ class NewsCategory {
|
|||||||
case "tech":
|
case "tech":
|
||||||
$cat = self::TECHNOLOGY;
|
$cat = self::TECHNOLOGY;
|
||||||
break;
|
break;
|
||||||
|
case "social":
|
||||||
|
$cat = self::SOCIAL;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return new NewsCategory($cat);
|
return new NewsCategory($cat);
|
||||||
}
|
}
|
||||||
@ -115,6 +122,8 @@ class NewsCategory {
|
|||||||
return "fas fa-futbol";
|
return "fas fa-futbol";
|
||||||
case self::TECHNOLOGY:
|
case self::TECHNOLOGY:
|
||||||
return "fas fa-laptop";
|
return "fas fa-laptop";
|
||||||
|
case self::SOCIAL:
|
||||||
|
return "fas fa-share-alt";
|
||||||
default:
|
default:
|
||||||
return "fas fa-newspaper";
|
return "fas fa-newspaper";
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ class NewsSource_NewsAPI extends NewsSource {
|
|||||||
$params['category'] = $category->toString();
|
$params['category'] = $category->toString();
|
||||||
}
|
}
|
||||||
$items = [];
|
$items = [];
|
||||||
$json = ApiFetcher::get($url, $params, ["apiKey" => $apikey]);
|
$json = ApiFetcher::get($url, $params, ["apiKey" => $apikey], "+1 hour");
|
||||||
$data = json_decode($json, TRUE);
|
$data = json_decode($json, TRUE);
|
||||||
if ($data['status'] != "ok") {
|
if ($data['status'] != "ok") {
|
||||||
return [];
|
return [];
|
||||||
|
@ -15,13 +15,13 @@ class NewsSource_Reddit extends NewsSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadItems() {
|
function loadItems() {
|
||||||
$this->loadSubreddit("news+worldnews+UpliftingNews", new NewsCategory(NewsCategory::GENERAL));
|
$this->loadSubreddit("popular/top", new NewsCategory(NewsCategory::SOCIAL));
|
||||||
//$this->loadSubreddit("technology", new NewsCategory(NewsCategory::TECHNOLOGY));
|
//$this->loadSubreddit("technology", new NewsCategory(NewsCategory::TECHNOLOGY));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadSubreddit(string $subreddit, NewsCategory $category) {
|
private function loadSubreddit(string $subreddit, NewsCategory $category) {
|
||||||
$items = [];
|
$items = [];
|
||||||
$json = ApiFetcher::get("https://www.reddit.com/r/$subreddit.json", ["limit" => "50"]);
|
$json = ApiFetcher::get("https://www.reddit.com/r/$subreddit.json", ["limit" => "50", "sort" => "top", "t" => "day"]);
|
||||||
$news = json_decode($json, TRUE)['data']['children'];
|
$news = json_decode($json, TRUE)['data']['children'];
|
||||||
foreach ($news as $d) {
|
foreach ($news as $d) {
|
||||||
$n = $d['data'];
|
$n = $d['data'];
|
||||||
@ -58,6 +58,8 @@ class NewsSource_Reddit extends NewsSource {
|
|||||||
$source = $n['domain'];
|
$source = $n['domain'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$source .= " via <a href=\"https://reddit.com$n[permalink]\" target=\"_BLANK\">" . $n['subreddit_name_prefixed'] . "</a>";
|
||||||
|
|
||||||
$timestamp = time();
|
$timestamp = time();
|
||||||
if (!empty($n['created'])) {
|
if (!empty($n['created'])) {
|
||||||
$timestamp = $n['created'];
|
$timestamp = $n['created'];
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use \SKleeschulte\Base32;
|
||||||
|
|
||||||
class Thumbnail {
|
class Thumbnail {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,7 +31,6 @@ class Thumbnail {
|
|||||||
$output = imagecreatetruecolor($width, $height);
|
$output = imagecreatetruecolor($width, $height);
|
||||||
imagecopyresampled($output, $image, 0, 0, 0, 0, $width, $height, ImageSX($image), ImageSY($image));
|
imagecopyresampled($output, $image, 0, 0, 0, 0, $width, $height, ImageSX($image), ImageSY($image));
|
||||||
|
|
||||||
ob_end_flush();
|
|
||||||
ob_start();
|
ob_start();
|
||||||
imagejpeg($output, null, 75);
|
imagejpeg($output, null, 75);
|
||||||
$imagedata = ob_get_contents();
|
$imagedata = ob_get_contents();
|
||||||
@ -40,21 +41,26 @@ class Thumbnail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes some data to base64.
|
* Make a thumbnail, save it to the disk cache, and return a url relative to
|
||||||
* @param type $img
|
* the app root.
|
||||||
|
* @param string $url
|
||||||
|
* @param int $width
|
||||||
|
* @param int,bool $height
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
static function imgToBase64($img): string {
|
static function getThumbnailCacheURL(string $url, int $width = 150, $height = true): string {
|
||||||
return base64_encode($img);
|
$encodedfilename = Base64::encode($url);
|
||||||
|
$path = "cache/thumb/$encodedfilename.$width.jpg";
|
||||||
|
|
||||||
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static function addThumbnailToCache(string $url, int $width = 150, $height = true) {
|
||||||
* Get the base64 data: URI for a jpeg image
|
$encodedfilename = Base64::encode($url);
|
||||||
* @param type $img
|
$path = "cache/thumb/$encodedfilename.$width.jpg";
|
||||||
* @return string
|
$image = self::getThumbnailFromUrl($url, $width, $height);
|
||||||
*/
|
file_put_contents(__DIR__ . "/../$path", $image);
|
||||||
static function jpegToBase64URI($img): string {
|
return $image;
|
||||||
return "data:image/jpeg;base64," . self::imgToBase64($img);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
11
lib/Weather_DarkSky.lib.php
Normal file
11
lib/Weather_DarkSky.lib.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Weather_DarkSky extends Weather {
|
||||||
|
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file demonstrates creating a form with the FormBuilder class.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$form = new FormBuilder("Sample Form", "fas fa-code", "", "GET");
|
|
||||||
|
|
||||||
$form->setID("sampleform");
|
|
||||||
|
|
||||||
$form->addHiddenInput("page", "form");
|
|
||||||
|
|
||||||
$form->addInput("name", "John", "text", true, null, null, "Your name", "fas fa-user", 6, 5, 20, "John(ny)?|Steve", "Invalid name, please enter John, Johnny, or Steve.");
|
|
||||||
$form->addInput("location", "", "select", true, null, ["1" => "Here", "2" => "There"], "Location", "fas fa-map-marker");
|
|
||||||
$form->addInput("textbox", "Hello world", "textarea", true, null, null, "Text area", "fas fa-font");
|
|
||||||
$form->addInput("box", "1", "checkbox", true, null, null, "I agree to the terms of service");
|
|
||||||
|
|
||||||
$form->addButton("Submit", "fas fa-save", null, "submit", "savebtn");
|
|
||||||
|
|
||||||
$form->generate();
|
|
@ -16,19 +16,17 @@ foreach ($newsitems as $item) {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
<i class="fas fa-newspaper fa-fw"></i> <?php $Strings->get("Headlines"); ?>
|
||||||
|
</p>
|
||||||
<?php
|
<?php
|
||||||
foreach ($itemsbycategory as $category => $items) {
|
|
||||||
$cat = NewsCategory::fromString($category);
|
?>
|
||||||
?>
|
<div class="list-group mb-4">
|
||||||
<p class="lead">
|
|
||||||
<i class="<?php echo $cat->getIcon(); ?> fa-fw"></i> <?php $Strings->get($cat->toString()); ?>
|
|
||||||
</p>
|
|
||||||
<div class="list-group mb-4">
|
|
||||||
<?php
|
<?php
|
||||||
shuffle($items);
|
|
||||||
$count = 0;
|
$count = 0;
|
||||||
foreach ($items as $item) {
|
foreach ($itemsbycategory["general"] as $item) {
|
||||||
if ($count >= 5) {
|
if ($count >= 6) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$count++;
|
$count++;
|
||||||
@ -42,13 +40,12 @@ foreach ($itemsbycategory as $category => $items) {
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<?php if (!empty($item->getImage())) { ?>
|
<?php if (!empty($item->getImage())) { ?>
|
||||||
<img src="<?php echo Thumbnail::jpegToBase64URI(Thumbnail::getThumbnailFromUrl($item->getImage(), 100)); ?>" alt="" class="img-fluid">
|
<img src="<?php echo Thumbnail::getThumbnailCacheURL($item->getImage(), 100); ?>" alt="" class="img-fluid">
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
}
|
|
||||||
?>
|
?>
|
@ -10,9 +10,19 @@ $newsitems = News::getItems();
|
|||||||
|
|
||||||
<div class="btn-toolbar mb-4" role="toolbar">
|
<div class="btn-toolbar mb-4" role="toolbar">
|
||||||
<div class="btn-group btn-group-toggle">
|
<div class="btn-group btn-group-toggle">
|
||||||
<?php foreach (NewsCategory::CATEGORIES as $cat) { ?>
|
<?php
|
||||||
|
foreach (NewsCategory::CATEGORIES as $cat) {
|
||||||
|
$category = NewsCategory::fromString($cat);
|
||||||
|
?>
|
||||||
<label class="btn btn-secondary">
|
<label class="btn btn-secondary">
|
||||||
<input type="radio" name="newscategory" id="category-btn-<?php echo strtolower($cat); ?>" value="<?php echo strtolower($cat); ?>" autocomplete="off"> <?php $Strings->get($cat); ?>
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="newscategory"
|
||||||
|
id="category-btn-<?php echo $category->toString(); ?>"
|
||||||
|
value="<?php echo $category->toString(); ?>"
|
||||||
|
autocomplete="off" />
|
||||||
|
<i class="<?php echo $category->getIcon(); ?>"></i>
|
||||||
|
<?php $Strings->get($category->toString()); ?>
|
||||||
</label>
|
</label>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</div>
|
</div>
|
||||||
@ -24,14 +34,24 @@ $newsitems = News::getItems();
|
|||||||
<div class="col-12 col-sm-6 col-md-6 col-lg-4 px-1 m-0 grid__brick" data-groups='["<?php echo $item->getCategory()->toString(); ?>"]'>
|
<div class="col-12 col-sm-6 col-md-6 col-lg-4 px-1 m-0 grid__brick" data-groups='["<?php echo $item->getCategory()->toString(); ?>"]'>
|
||||||
<div class="card mb-2">
|
<div class="card mb-2">
|
||||||
<?php if (!empty($item->getImage())) { ?>
|
<?php if (!empty($item->getImage())) { ?>
|
||||||
<img src="<?php echo $item->getImage(); ?>" class="card-img-top" alt="">
|
<a href="<?php echo $item->getURL(); ?>" target="_BLANK">
|
||||||
|
<img src="<?php
|
||||||
|
if (strpos($item->getImage(), "preview.redd.it") !== false) {
|
||||||
|
echo $item->getImage();
|
||||||
|
} else {
|
||||||
|
echo Thumbnail::getThumbnailCacheURL($item->getImage(), 500);
|
||||||
|
}
|
||||||
|
?>" class="card-img-top" alt="">
|
||||||
|
</a>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
<a class="card-body text-dark" href="<?php echo $item->getURL(); ?>" target="_BLANK">
|
<div class="card-body">
|
||||||
|
<a class="text-dark" href="<?php echo $item->getURL(); ?>" target="_BLANK">
|
||||||
<h4 class="card-title">
|
<h4 class="card-title">
|
||||||
<?php echo htmlentities($item->getHeadline()); ?>
|
<?php echo htmlentities($item->getHeadline()); ?>
|
||||||
</h4>
|
</h4>
|
||||||
<p class="small"><?php echo htmlentities($item->getSource()); ?></p>
|
|
||||||
</a>
|
</a>
|
||||||
|
<p class="small"><?php echo $item->getSource(); ?></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
@ -40,6 +40,10 @@ $SETTINGS = [
|
|||||||
"apikeys" => [
|
"apikeys" => [
|
||||||
"newsapi.org" => "",
|
"newsapi.org" => "",
|
||||||
"darksky.net" => "",
|
"darksky.net" => "",
|
||||||
|
"twitter.com" => [
|
||||||
|
"consumer_key" => "",
|
||||||
|
"consumer_secret" => ""
|
||||||
|
]
|
||||||
],
|
],
|
||||||
// Which data sources to use
|
// Which data sources to use
|
||||||
"sources" => [
|
"sources" => [
|
||||||
@ -59,7 +63,7 @@ $SETTINGS = [
|
|||||||
// Language to use for localization. See langs folder to add a language.
|
// Language to use for localization. See langs folder to add a language.
|
||||||
"language" => "en",
|
"language" => "en",
|
||||||
// Shown in the footer of all the pages.
|
// Shown in the footer of all the pages.
|
||||||
"footer_text" => "Data sources: <a href=\"https://newsapi.org\">NewsAPI.org</a>, <a href=\"https://reddit.com\">Reddit</a>, <a href=\"https://darksky.net\">Dark Sky</a>",
|
"footer_text" => "News sources: <a href=\"https://newsapi.org\">NewsAPI.org</a>, <a href=\"https://reddit.com\">Reddit.com</a>. Weather <a href=\"https://darksky.net/poweredby/\">powered by Dark Sky</a>.",
|
||||||
// Also shown in the footer, but with "Copyright <current_year>" in front.
|
// Also shown in the footer, but with "Copyright <current_year>" in front.
|
||||||
"copyright" => "Netsyms Technologies",
|
"copyright" => "Netsyms Technologies",
|
||||||
// Base URL for building links relative to the location of the app.
|
// Base URL for building links relative to the location of the app.
|
||||||
|
@ -17,3 +17,5 @@ $("input[name=newscategory]").on("change", function () {
|
|||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
window.shuffleInstance.layout();
|
window.shuffleInstance.layout();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
window.shuffleInstance.filter("general");
|
Loading…
x
Reference in New Issue
Block a user