From 7623b3c178f6beaa42b7f6d0f5696c120145f835 Mon Sep 17 00:00:00 2001 From: Gandum2077 Date: Fri, 13 Mar 2020 11:25:29 +0800 Subject: [PATCH] add widget; fix issues about danbooru --- config.json | 4 +- main.js | 9 +- prefs.json | 6 ++ scripts/booru/filter.js | 7 +- scripts/booru/fix.js | 11 ++- scripts/controller.js | 63 +++++++------ scripts/utils/database.js | 41 ++++++--- scripts/views/widgetViews.js | 167 +++++++++++++++++++++++++++++++++++ scripts/widget.js | 11 +++ scripts/widgetController.js | 90 +++++++++++++++++++ strings/en.strings | 8 +- strings/zh-Hans.strings | 5 ++ 12 files changed, 378 insertions(+), 44 deletions(-) create mode 100644 scripts/views/widgetViews.js create mode 100644 scripts/widget.js create mode 100644 scripts/widgetController.js diff --git a/config.json b/config.json index 62f4ebc..203435e 100644 --- a/config.json +++ b/config.json @@ -2,10 +2,10 @@ "info": { "name": "JSBooru", "url": "", - "version": "2.0.4", + "version": "2.1.0", "author": "Gandum2077", "website": "https://github.com/Gandum2077/JSBooru", - "types": 1 + "types": 0 }, "settings": { "minSDKVer": "2.5.1", diff --git a/main.js b/main.js index c97b69c..284a273 100644 --- a/main.js +++ b/main.js @@ -1,2 +1,7 @@ -const app = require("scripts/app"); -app.init(); +if ($app.env === $env.app) { + const app = require("scripts/app"); + await app.init(); +} else { + const widget = require("scripts/widget"); + await widget.init(); +} diff --git a/prefs.json b/prefs.json index 7a42f7d..618f676 100644 --- a/prefs.json +++ b/prefs.json @@ -45,6 +45,12 @@ "type": "boolean", "key": "safe_search", "value": true + }, + { + "title": "Filter Nonauthorized Images", + "type": "boolean", + "key": "filter_nonauthorized_images", + "value": true } ] }, diff --git a/scripts/booru/filter.js b/scripts/booru/filter.js index 40de478..30ac7c5 100644 --- a/scripts/booru/filter.js +++ b/scripts/booru/filter.js @@ -2,6 +2,11 @@ function safeSearchFilter(items) { return items.filter(n => n.rating === "s"); } +function nonauthorizedFilter(items) { + return items.filter(n => n.previewUrl) +} + module.exports = { - safeSearchFilter + safeSearchFilter, + nonauthorizedFilter }; diff --git a/scripts/booru/fix.js b/scripts/booru/fix.js index 3dfe0d9..31f1071 100644 --- a/scripts/booru/fix.js +++ b/scripts/booru/fix.js @@ -66,7 +66,16 @@ const fixFunctions = { item.sampleUrl = `https://realbooru.com/samples/${directory}/sample_${basename}.jpg`; } return item; - } + }, + "danbooru.donmai.us": item => { + if (!item.fileUrl) { + item.fileUrl = item.data.file_url || null + } + if (!item.previewUrl) { + item.previewUrl = item.data.preview_file_url || null + } + return item; + }, }; function fix(item) { diff --git a/scripts/controller.js b/scripts/controller.js index f3f667c..49e6fa7 100644 --- a/scripts/controller.js +++ b/scripts/controller.js @@ -14,7 +14,7 @@ const { generatorForSite, generatorForFavorites } = require("./booru/generator"); -const { safeSearchFilter } = require("./booru/filter"); +const { safeSearchFilter, nonauthorizedFilter } = require("./booru/filter"); const { checkRandomSupport } = require("./utils/utils"); const SubController = require("./subController"); @@ -73,14 +73,21 @@ class Controller { }); this.views.thumbnailsViewBooru = new ThumbnailsView({ layout: (make, view) => { - make.left.right.bottom.inset(0) - make.top.inset(36) + make.left.right.bottom.inset(0); + make.top.inset(36); }, events: { itemSize: function(sender, indexPath) { - const index = indexPath.item - const info = classThis.booruItems[index] - return $size(info.width, info.height) + const index = indexPath.item; + const info = classThis.booruItems[index]; + const width = info.width; + let height = info.height; + if (height > width * 2) { + height = width * 2; + } else if (height < width / 2) { + height = width / 2; + } + return $size(width, height); }, pulled: async function(sender) { if (classThis.isLoading) { @@ -114,11 +121,7 @@ class Controller { } const result = await classThis.generatorBooru.next(); if (!result.done) { - if ($prefs.get("safe_search")) { - classThis.booruItems.push(...safeSearchFilter(result.value)); - } else { - classThis.booruItems.push(...result.value); - } + classThis.booruItems.push(...classThis._filter(result.value)); classThis.views.thumbnailsViewBooru.items = classThis.booruItems; } sender.endFetchingMore(); @@ -127,14 +130,21 @@ class Controller { }); this.views.thumbnailsViewFavorites = new ThumbnailsView({ layout: (make, view) => { - make.left.right.bottom.inset(0) - make.top.inset(36) + make.left.right.bottom.inset(0); + make.top.inset(36); }, events: { itemSize: function(sender, indexPath) { - const index = indexPath.item - const info = classThis.favoritesItems[index] - return $size(info.width, info.height) + const index = indexPath.item; + const info = classThis.favoritesItems[index]; + const width = info.width; + let height = info.height; + if (height > width * 2) { + height = width * 2; + } else if (height < width / 2) { + height = width / 2; + } + return $size(width, height); }, pulled: async function(sender) { classThis.loadFavorites({ @@ -161,7 +171,8 @@ class Controller { const result = classThis.generatorFavorites.next(); if (!result.done) { classThis.favoritesItems.push(...result.value); - classThis.views.thumbnailsViewFavorites.items = classThis.favoritesItems; + classThis.views.thumbnailsViewFavorites.items = + classThis.favoritesItems; } sender.endFetchingMore(); } @@ -178,7 +189,7 @@ class Controller { const tags = text.split(" "); if (!tags || !tags.length) return; await classThis.loadBooru({ tags }); - constants.userConfig.addSearchHistory(text) + constants.userConfig.addSearchHistory(text); } }); this.views.searchBarFavorites = new SearchBar({ @@ -200,7 +211,7 @@ class Controller { const tags = text.split(" "); if (!tags || !tags.length) return; await classThis.loadBooru({ tags }); - constants.userConfig.addSearchHistory(text) + constants.userConfig.addSearchHistory(text); } }); } @@ -441,7 +452,7 @@ class Controller { case 2: { this.views.tagsView.moveToFront(); $ui.title = $l10n("TAGS"); - this.views.tagsView.reload() + this.views.tagsView.reload(); break; } default: @@ -468,11 +479,7 @@ class Controller { startPage }); const result = await this.generatorBooru.next(); - if ($prefs.get("safe_search")) { - this.booruItems = safeSearchFilter(result.value); - } else { - this.booruItems = result.value; - } + this.booruItems = this._filter(result.value); this.views.thumbnailsViewBooru.items = this.booruItems; this.views.thumbnailsViewBooru.view.scrollTo({ indexPath: $indexPath(0, 0), @@ -504,6 +511,12 @@ class Controller { this.favoritesInfo.startPage = startPage; } + _filter(items) { + if ($prefs.get("safe_search")) items = safeSearchFilter(items); + if ($prefs.get("filter_nonauthorized_images")) items = nonauthorizedFilter(items); + return items + } + async openPrefs() { await $prefs.open(); this.checkPrefs(); diff --git a/scripts/utils/database.js b/scripts/utils/database.js index e94a118..5fb1c22 100644 --- a/scripts/utils/database.js +++ b/scripts/utils/database.js @@ -79,9 +79,9 @@ class Database { } insertPost({ info, favorited }) { - const { id, previewUrl: preview_url } = info - const site = info.booru.domain - const tags = ' ' + info.tags.join(' ') + ' ' + const { id, previewUrl: preview_url } = info; + const site = info.booru.domain; + const tags = " " + info.tags.join(" ") + " "; this.deletePost({ site, id }); this.db.update({ sql: @@ -151,6 +151,22 @@ class Database { }; } + getRandomPost({ limit = 4 } = {}) { + const clause = + "SELECT * FROM posts WHERE order_id IN (SELECT order_id FROM posts ORDER BY RANDOM() LIMIT ?)"; + const args = [limit] + const result = this.search(clause, args); + return result.map(n => { + return { + site_id: n.site_id, + id: n.id, + thumbnail_url: n.thumbnail_url, + info: JSON.parse(n.info), + favorited: n.favorited ? true : false + }; + }); + } + getPostCount(favorited = true) { const where_clause = favorited ? " WHERE favorited = 1" : ""; const clause = "SELECT COUNT() FROM posts" + where_clause; @@ -229,7 +245,8 @@ class Database { updateSavedTag({ name, title, category, favorited }) { this.db.update({ - sql: "UPDATE saved_tags SET title = ?, category = ?, favorited = ? WHERE name=?", + sql: + "UPDATE saved_tags SET title = ?, category = ?, favorited = ? WHERE name=?", args: [title, category, favorited, name] }); this.db.commit(); @@ -268,17 +285,17 @@ class Database { } } - safeAddSavedTag({name, title, category, favorited }) { - const result = this.searchSavedTag(name) + safeAddSavedTag({ name, title, category, favorited }) { + const result = this.searchSavedTag(name); if (result) { - this.updateSavedTag({ name, title, category, favorited }) + this.updateSavedTag({ name, title, category, favorited }); } else { - this.insertSavedTag({ name, title, category, favorited }) + this.insertSavedTag({ name, title, category, favorited }); } } renameCategory(oldname, newname) { - if (!newname) newname = null + if (!newname) newname = null; this.db.update({ sql: "UPDATE saved_tags SET category = ? WHERE category = ?", args: [newname, oldname] @@ -333,11 +350,11 @@ class Database { } safeAddCombination({ name, title }) { - const result = this.searchSavedCombination(name) + const result = this.searchSavedCombination(name); if (result) { - this.updateSavedCombination({name, title}) + this.updateSavedCombination({ name, title }); } else { - this.insertSavedCombination({name, title}) + this.insertSavedCombination({ name, title }); } } } diff --git a/scripts/views/widgetViews.js b/scripts/views/widgetViews.js new file mode 100644 index 0000000..70fcd2a --- /dev/null +++ b/scripts/views/widgetViews.js @@ -0,0 +1,167 @@ +const BaseView = require("../components/baseView"); + +class TopBar extends BaseView { + constructor({ symbol, title, tapped, layout }) { + super(); + this.symbol = symbol; + this.title = title; + this.tapped = tapped; + this.layout = layout; + } + + _defineView() { + return { + type: "view", + props: { + id: this.id + }, + views: [ + { + type: "image", + props: { + symbol: this.symbol, + contentMode: 4 + }, + layout: (make, view) => { + make.top.bottom.inset(0); + make.left.inset(5); + make.width.equalTo(view.height); + } + }, + { + type: "label", + props: { + text: this.title, + font: $font(17) + }, + layout: (make, view) => { + make.left.equalTo(view.prev.right).inset(5); + make.top.bottom.inset(0); + make.width.equalTo(150); + } + }, + { + type: "button", + props: { + symbol: "arrow.2.circlepath", + title: $l10n("REFRESH_WIDGET"), + font: $font(17), + titleColor: $color("black"), + bgcolor: $color("clear") + }, + layout: (make, view) => { + make.top.bottom.inset(0); + make.right.inset(5); + }, + events: { + tapped: this.tapped + } + } + ], + layout: this.layout + }; + } +} + +class ThumbnailsView extends BaseView { + constructor({ columns = 3, layout, events }) { + super(); + this.columns = columns; + this.layout = layout; + this.events = events; + } + + _defineView() { + return { + type: "matrix", + props: { + id: this.id, + columns: this.columns, + spacing: 3, + scrollEnabled: false, + square: true, + template: { + props: { + bgcolor: $color("clear") + }, + views: [ + { + type: "image", + props: { + id: "image", + contentMode: 2 + }, + layout: $layout.fill + } + ] + } + }, + layout: this.layout, + events: this.events + }; + } + + set urls(urls) { + this.view.data = urls.map(n => { + return { image: { src: n } }; + }); + } +} + +class ImageView extends BaseView { + constructor({ hidden = true, layout = $layout.fill, tapped }) { + super(); + this._hidden = hidden; + this.layout = layout; + this.tapped = tapped; + } + + _defineView() { + return { + type: "image", + props: { + id: this.id, + hidden: this._hidden, + userInteractionEnabled: true, + contentMode: 1 + }, + layout: this.layout, + events: { + tapped: this.tapped + } + }; + } +} + +class TipsLabel extends BaseView { + constructor({ + tips = $l10n("WIDGET_TIPS"), + layout= $layout.fill + } = {}) { + super() + this.tips = tips + this.layout = layout + } + + _defineView() { + return { + type: "label", + props: { + id: this.id, + text: this.tips, + hidden: true, + align: $align.center, + bgcolor: $color("clear"), + textColor: $color("black") + }, + layout: this.layout + } + } +} + +module.exports = { + TopBar, + ThumbnailsView, + ImageView, + TipsLabel +}; diff --git a/scripts/widget.js b/scripts/widget.js new file mode 100644 index 0000000..3c0d148 --- /dev/null +++ b/scripts/widget.js @@ -0,0 +1,11 @@ +const Controller = require("./widgetController"); + +async function init() { + const controller = new Controller(); + controller.render(); + controller.refresh(); +} + +module.exports = { + init +}; diff --git a/scripts/widgetController.js b/scripts/widgetController.js new file mode 100644 index 0000000..8f6a1d1 --- /dev/null +++ b/scripts/widgetController.js @@ -0,0 +1,90 @@ +const database = require("./utils/database"); +const { TopBar, ThumbnailsView, ImageView, TipsLabel } = require("./views/widgetViews"); +const { ContentView } = require("./views/views"); + +class Controller { + constructor() { + this.views = {}; + this._createdPermanentView(); + } + + _createdPermanentView() { + const classThis = this; + this.views.main = new ContentView({ bgcolor: $color("clear") }); + this.views.topBar = new TopBar({ + symbol: "bookmark.fill", + title: $l10n("FAVORITES"), + tapped: sender => classThis.refresh(), + layout: (make, view) => { + make.top.left.right.inset(0); + make.height.equalTo(30); + } + }); + this.views.thumbnailsView = new ThumbnailsView({ + layout: (make, view) => { + make.top.equalTo(this.views.topBar.view.bottom).inset(5); + make.left.right.bottom.inset(0); + }, + events: { + didSelect: (sender, indexPath, data) => { + classThis.expand(data.image.src) + } + } + }); + this.views.imageView = new ImageView({ + hidden: true, + tapped: sender => { + classThis.collapse() + } + }); + this.views.tipsLabel = new TipsLabel() + } + + render() { + $ui.render({ + props: { + navBarHidden: true + }, + views: [this.views.main.definition] + }); + this.views.main.add(this.views.topBar.definition); + this.views.main.add(this.views.thumbnailsView.definition); + this.views.main.add(this.views.imageView.definition); + this.views.main.add(this.views.tipsLabel.definition); + } + + refresh() { + const urls = database + .getRandomPost({limit: 3}) + .map(n => + $prefs.get("orginal_image_first") ? n.info.fileUrl : n.info.sampleUrl + ); + if (urls.length) { + this.views.thumbnailsView.urls = urls; + } else { + this.showTips() + } + } + + expand(src) { + this.views.topBar.view.hidden = true + this.views.thumbnailsView.view.hidden = true + this.views.imageView.view.hidden = false + this.views.imageView.view.src = src + } + + collapse() { + this.views.topBar.view.hidden = false + this.views.thumbnailsView.view.hidden = false + this.views.imageView.view.hidden = true + } + + showTips() { + this.views.topBar.view.hidden = true + this.views.thumbnailsView.view.hidden = true + this.views.imageView.view.hidden = true + this.views.tipsLabel.view.hidden = false + } +} + +module.exports = Controller; diff --git a/strings/en.strings b/strings/en.strings index 0a678cf..2831db9 100644 --- a/strings/en.strings +++ b/strings/en.strings @@ -14,6 +14,7 @@ "Show Favorites on Startup" = "Show Favorites on Startup"; "SEARCH" = "SEARCH"; "Safe Search" = "Safe Search"; +"Filter Nonauthorized Images" = "Filter Nonauthorized Images"; "Tags Shown on Search" = "Tags Shown on Search"; "Search History" = "Search History"; "Favorited Tags" = "Favorited Tags"; @@ -88,4 +89,9 @@ "MANAGE_CATEGORIES" = "Manage Categories"; "RANDOM_SUPPORT_ERROR" = "Random search is not supported by this site"; -"CURRENT" = "Current"; \ No newline at end of file +"CURRENT" = "Current"; + +// widget + +"REFRESH_WIDGET" = "Refresh"; +"WIDGET_TIPS" = "No Favorited Images"; \ No newline at end of file diff --git a/strings/zh-Hans.strings b/strings/zh-Hans.strings index ff36c92..116487e 100644 --- a/strings/zh-Hans.strings +++ b/strings/zh-Hans.strings @@ -14,6 +14,7 @@ "Show Favorites on Startup" = "启动后显示收藏"; "SEARCH" = "搜索"; "Safe Search" = "安全搜索"; +"Filter Nonauthorized Images" = "过滤无法显示的图片"; "Tags Shown on Search" = "搜索时显示的标签"; "Search History" = "历史搜索"; "Favorited Tags" = "收藏的标签"; @@ -90,3 +91,7 @@ "RANDOM_SUPPORT_ERROR" = "此站点不支持随即搜索"; "CURRENT" = "当前"; +// widget + +"REFRESH_WIDGET" = "换一批"; +"WIDGET_TIPS" = "尚未收藏图片哦"; \ No newline at end of file