Add the option to search a better icon on the html page

master
kirbylife 2024-04-04 01:14:25 -06:00
parent 33ebb4bdd4
commit 4c9b7da927
4 changed files with 197 additions and 91 deletions

View File

@ -1,2 +1,17 @@
*Mon Apr 27 2015 Tobi Sim 0.0.1-1
-initial commit
*Mon Feb 13 2023 kirbylife <hola@kirbylife.dev> 0.5.0-7
- Add SVG support
- Improve performance
- Add resize slider
- Fix irregluar size of icons on the main grid
- Add the option to search a better icon directly on the html page
- Improve a bit the logging
*Mon Feb 13 2023 kirbylife <hola@kirbylife.dev> 0.4.5-5
- Add aarch64 support
- Add logging file
- Skip the sailjail permissions
- Remove all the third-repo dependencies
- Now, when you re-edit an icon, the original icon is used instead of the edited one
*Mon May 17 2021 kirbylife <hola@kirbylife.dev> 0.4.0-1
- First published version

View File

@ -1,6 +1,7 @@
import QtQuick 2.0
import Sailfish.Silica 1.0
import io.thp.pyotherside 1.3
import Nemo.Notifications 1.0
Page {
id: iconEditor
@ -8,44 +9,106 @@ Page {
property var attrs
property var corners: [true, true, true, true]
PageHeader {
id: header
width: parent.width
title: "name: " + attrs.Name
Notification {
id: notification
appIcon: "/usr/share/icons/hicolor/86x86/apps/harbour-muchkin"
expireTimeout: 5000
function sendMessage(msg) {
notification.previewBody = msg
notification.publish()
}
}
SilicaGridView {
anchors.top: header.bottom
width: parent.width
height: width
cellWidth: width / 2
cellHeight: cellWidth
SilicaFlickable {
id: mainList
anchors.fill: parent
model: ListModel {
id: gridItems
ListElement {
idCell: 0
activated: true
}
ListElement {
idCell: 1
activated: true
}
ListElement {
idCell: 2
activated: true
}
ListElement {
idCell: 3
activated: true
PullDownMenu {
MenuItem {
text: "Search a better icon"
onClicked: {
py.importModule("main", function(){
py.call("main.scrape_icon", [iconEditor.attrs.URL], function(result){
if(result) {
icon.source = result;
iconEditor.attrs.Icon = result;
notification.sendMessage("A better icon found");
} else {
notification.sendMessage("Not better icon found");
}
})
})
}
}
}
delegate: GridItem {
onClicked: {
activated = !activated
iconEditor.corners[idCell] = activated
PageHeader {
id: header
width: parent.width
title: "name: " + attrs.Name
}
SilicaGridView {
anchors.top: header.bottom
width: parent.width
height: width
cellWidth: width / 2
cellHeight: cellWidth
model: ListModel {
id: gridItems
ListElement {
idCell: 0
activated: true
}
ListElement {
idCell: 1
activated: true
}
ListElement {
idCell: 2
activated: true
}
ListElement {
idCell: 3
activated: true
}
}
delegate: GridItem {
onClicked: {
activated = !activated
iconEditor.corners[idCell] = activated
py.importModule("main", function(){
py.call("main.sailify", [(iconEditor.attrs.old_icon || iconEditor.attrs.Icon).toString(), iconEditor.corners, size.value], function(result){
icon.source = result
})
})
}
}
}
Image {
id: icon
anchors.top: header.bottom
width: parent.width
height: width
}
Slider {
id: size
anchors.top: icon.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
label: "Size:"
width: parent.width
minimumValue: 0
maximumValue: 100
stepSize: 10
value: 100
onReleased: {
py.importModule("main", function(){
py.call("main.sailify", [(iconEditor.attrs.old_icon || iconEditor.attrs.Icon).toString(), iconEditor.corners, size.value], function(result){
icon.source = result
@ -53,66 +116,41 @@ Page {
})
}
}
}
Image {
id: icon
anchors.top: header.bottom
width: parent.width
height: width
}
Slider {
id: size
anchors.top: icon.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
label: "Size:"
width: parent.width
minimumValue: 0
maximumValue: 100
stepSize: 10
value: 100
onReleased: {
py.importModule("main", function(){
py.call("main.sailify", [(iconEditor.attrs.old_icon || iconEditor.attrs.Icon).toString(), iconEditor.corners, size.value], function(result){
icon.source = result
Button {
anchors.top: size.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 20
text: "Save"
onClicked: {
if(iconEditor.attrs.old_icon === undefined) {
iconEditor.attrs.old_icon = iconEditor.attrs.Icon.toString()
}
iconEditor.attrs.Icon = icon.source.toString()
py.importModule("main", function(){
py.call("main.save_icon", [iconEditor.attrs], function(result){
if(result){
pageStack.pop()
pageStack.completeAnimation()
pageStack.currentPage.mainList.reload()
}
})
})
})
}
}
Button {
anchors.top: size.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 20
text: "Save"
onClicked: {
if(iconEditor.attrs.old_icon === undefined) {
iconEditor.attrs.old_icon = iconEditor.attrs.Icon.toString()
}
iconEditor.attrs.Icon = icon.source.toString()
py.importModule("main", function(){
py.call("main.save_icon", [iconEditor.attrs], function(result){
if(result){
pageStack.pop()
pageStack.completeAnimation()
pageStack.currentPage.mainList.reload()
}
})
})
}
Python {
id: py
Component.onCompleted: {
py.addImportPath(Qt.resolvedUrl("../src"));
py.importModule("main", function(){
py.call("main.sailify", [(iconEditor.attrs.old_icon || iconEditor.attrs.Icon).toString(), [1, 1, 1, 1], size.value], function(result){
icon.source = result
})
})
}
}
}
Python {
id: py
Component.onCompleted: {
py.addImportPath(Qt.resolvedUrl("../src"));
py.importModule("main", function(){
py.call("main.sailify", [(iconEditor.attrs.old_icon || iconEditor.attrs.Icon).toString(), [1, 1, 1, 1], size.value], function(result){
icon.source = result
})
})
}
}
}

13
src/decorators.py 100644
View File

@ -0,0 +1,13 @@
import logging
def log_error(f):
def wrap(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
logging.error(f"Error on: {f.__name__}")
logging.error(f"args: {args}")
logging.error(f"kwargs: {kwargs}")
logging.error(f"Exception: {e}")
raise e
return wrap

View File

@ -1,4 +1,5 @@
import pyotherside # pyright: ignore reportMissingImports
from decorators import log_error
from PIL import Image, ImageDraw, ImageChops
from PIL.Image import Image as ImageType
from io import BytesIO
@ -6,7 +7,9 @@ from glob import glob
from collections import Counter
from subprocess import getstatusoutput
from urllib.parse import urlparse
from urllib.parse import urljoin
from functools import lru_cache
from lxml import etree
import requests
import base64
import os
@ -16,6 +19,10 @@ import tempfile
ICON_SIZE = (256, 256)
HEADERS = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"
}
logging_filename = os.path.join(os.path.expanduser("~"),
".local",
"share",
@ -69,7 +76,7 @@ def avg_colors(img):
def add_background(img: ImageType, bg_tuple: tuple, size: float) -> ImageType:
img = img.copy()
size = size / 100
size = max(size, 1) / 100
bg = Image.new("RGBA", img.size, bg_tuple)
img.thumbnail((value * size for value in img.size))
bg.paste(img, ((bg.size[0] - img.size[0]) // 2, (bg.size[1] - img.size[1]) // 2), img)
@ -123,6 +130,39 @@ def get_web_icons():
return list(map(parse_file, apps))
@log_error
def scrape_icon(url: str) -> str:
url_parsed = urlparse(url)
origin = f"{url_parsed.scheme}://{url_parsed.netloc}"
html = etree.HTML(requests.get(origin, headers=HEADERS).text)
possible_svg = None
possible_icon = None
max_size = 0
for icon in html.xpath("//link[contains(@rel, 'icon')]"):
if "SVG" in next(iter(icon.xpath("@type")), "").upper():
possible_svg = icon.xpath("@href")[0]
break
possible_href = next(iter(icon.xpath("@href")), None)
possible_size = next(iter(icon.xpath("@sizes")), None)
possible_rel = next(iter(icon.xpath("@rel")), "")
if possible_size and possible_href and possible_size != "any":
size = int(possible_size.split("x")[0])
if size > max_size:
max_size = size
possible_icon = possible_href
if possible_href and not possible_size and possible_rel.upper() == "apple-touch-icon".upper():
possible_icon = possible_href
new_icon = possible_svg or possible_icon
if new_icon:
parsed_new_icon = urlparse(new_icon)
if not parsed_new_icon.scheme or not parsed_new_icon.netloc:
new_icon = urljoin(origin, new_icon)
return sailify(new_icon, [True] * 4, 100)
@log_error
def sailify(raw_str, pattern, size) -> str:
url_parsed = urlparse(raw_str)
if url_parsed.scheme == "data":