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 *Mon Feb 13 2023 kirbylife <hola@kirbylife.dev> 0.5.0-7
-initial commit - 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 QtQuick 2.0
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
import io.thp.pyotherside 1.3 import io.thp.pyotherside 1.3
import Nemo.Notifications 1.0
Page { Page {
id: iconEditor id: iconEditor
@ -8,44 +9,106 @@ Page {
property var attrs property var attrs
property var corners: [true, true, true, true] property var corners: [true, true, true, true]
PageHeader { Notification {
id: header id: notification
width: parent.width appIcon: "/usr/share/icons/hicolor/86x86/apps/harbour-muchkin"
title: "name: " + attrs.Name expireTimeout: 5000
function sendMessage(msg) {
notification.previewBody = msg
notification.publish()
}
} }
SilicaGridView { SilicaFlickable {
anchors.top: header.bottom id: mainList
width: parent.width anchors.fill: parent
height: width
cellWidth: width / 2
cellHeight: cellWidth
model: ListModel { PullDownMenu {
id: gridItems MenuItem {
text: "Search a better icon"
ListElement { onClicked: {
idCell: 0 py.importModule("main", function(){
activated: true py.call("main.scrape_icon", [iconEditor.attrs.URL], function(result){
} if(result) {
ListElement { icon.source = result;
idCell: 1 iconEditor.attrs.Icon = result;
activated: true notification.sendMessage("A better icon found");
} } else {
ListElement { notification.sendMessage("Not better icon found");
idCell: 2 }
activated: true })
} })
ListElement { }
idCell: 3
activated: true
} }
} }
delegate: GridItem { PageHeader {
onClicked: { id: header
activated = !activated width: parent.width
iconEditor.corners[idCell] = activated 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.importModule("main", function(){
py.call("main.sailify", [(iconEditor.attrs.old_icon || iconEditor.attrs.Icon).toString(), iconEditor.corners, size.value], function(result){ py.call("main.sailify", [(iconEditor.attrs.old_icon || iconEditor.attrs.Icon).toString(), iconEditor.corners, size.value], function(result){
icon.source = result icon.source = result
@ -53,66 +116,41 @@ Page {
}) })
} }
} }
}
Image { Button {
id: icon anchors.top: size.bottom
anchors.top: header.bottom anchors.horizontalCenter: parent.horizontalCenter
width: parent.width anchors.topMargin: 20
height: width text: "Save"
} onClicked: {
if(iconEditor.attrs.old_icon === undefined) {
Slider { iconEditor.attrs.old_icon = iconEditor.attrs.Icon.toString()
id: size }
anchors.top: icon.bottom iconEditor.attrs.Icon = icon.source.toString()
anchors.horizontalCenter: parent.horizontalCenter py.importModule("main", function(){
anchors.topMargin: 10 py.call("main.save_icon", [iconEditor.attrs], function(result){
label: "Size:" if(result){
width: parent.width pageStack.pop()
minimumValue: 0 pageStack.completeAnimation()
maximumValue: 100 pageStack.currentPage.mainList.reload()
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()
}
})
})
} }
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 import pyotherside # pyright: ignore reportMissingImports
from decorators import log_error
from PIL import Image, ImageDraw, ImageChops from PIL import Image, ImageDraw, ImageChops
from PIL.Image import Image as ImageType from PIL.Image import Image as ImageType
from io import BytesIO from io import BytesIO
@ -6,7 +7,9 @@ from glob import glob
from collections import Counter from collections import Counter
from subprocess import getstatusoutput from subprocess import getstatusoutput
from urllib.parse import urlparse from urllib.parse import urlparse
from urllib.parse import urljoin
from functools import lru_cache from functools import lru_cache
from lxml import etree
import requests import requests
import base64 import base64
import os import os
@ -16,6 +19,10 @@ import tempfile
ICON_SIZE = (256, 256) 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("~"), logging_filename = os.path.join(os.path.expanduser("~"),
".local", ".local",
"share", "share",
@ -69,7 +76,7 @@ def avg_colors(img):
def add_background(img: ImageType, bg_tuple: tuple, size: float) -> ImageType: def add_background(img: ImageType, bg_tuple: tuple, size: float) -> ImageType:
img = img.copy() img = img.copy()
size = size / 100 size = max(size, 1) / 100
bg = Image.new("RGBA", img.size, bg_tuple) bg = Image.new("RGBA", img.size, bg_tuple)
img.thumbnail((value * size for value in img.size)) 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) 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)) 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: def sailify(raw_str, pattern, size) -> str:
url_parsed = urlparse(raw_str) url_parsed = urlparse(raw_str)
if url_parsed.scheme == "data": if url_parsed.scheme == "data":