Update
@@ -0,0 +1,30 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
__version__ = "2.20.2"
|
||||
|
||||
|
||||
def version():
|
||||
"""
|
||||
Return the current version of the Studio Library
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return __version__
|
||||
|
||||
|
||||
from studiolibrary import config
|
||||
from studiolibrary import resource
|
||||
from studiolibrary.utils import *
|
||||
from studiolibrary.library import Library
|
||||
from studiolibrary.libraryitem import LibraryItem
|
||||
from studiolibrary.main import main
|
||||
@@ -0,0 +1,94 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
_config = None
|
||||
|
||||
|
||||
def get(*args):
|
||||
"""
|
||||
Get values from the config.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
global _config
|
||||
|
||||
if not _config:
|
||||
_config = read(paths())
|
||||
|
||||
return _config.get(*args)
|
||||
|
||||
|
||||
def set(key, value):
|
||||
|
||||
global _config
|
||||
|
||||
if not _config:
|
||||
_config = read(paths())
|
||||
|
||||
_config[key] = value
|
||||
|
||||
|
||||
def paths():
|
||||
"""
|
||||
Return all possible config paths.
|
||||
|
||||
:rtype: list[str]
|
||||
"""
|
||||
cwd = os.path.dirname(__file__)
|
||||
paths_ = [os.path.join(cwd, "config", "default.json")]
|
||||
|
||||
path = os.environ.get("STUDIO_LIBRARY_CONFIG_PATH")
|
||||
path = path or os.path.join(cwd, "config", "config.json")
|
||||
|
||||
if not os.path.exists(path):
|
||||
cwd = os.path.dirname(os.path.dirname(cwd))
|
||||
path = os.path.join(cwd, "config", "config.json")
|
||||
|
||||
if os.path.exists(path):
|
||||
paths_.append(path)
|
||||
|
||||
return paths_
|
||||
|
||||
|
||||
def read(paths):
|
||||
"""
|
||||
Read all paths and overwrite the keys with each successive file.
|
||||
|
||||
A custom config parser for passing JSON files.
|
||||
|
||||
We use this instead of the standard ConfigParser as the JSON format
|
||||
can support list and dict types.
|
||||
|
||||
This parser can also support comments using the following style "//"
|
||||
|
||||
:type paths: list[str]
|
||||
:rtype: dict
|
||||
"""
|
||||
conf = {}
|
||||
|
||||
for path in paths:
|
||||
lines = []
|
||||
|
||||
with open(path) as f:
|
||||
for line in f.readlines():
|
||||
if not line.strip().startswith('//'):
|
||||
lines.append(line)
|
||||
|
||||
data = '\n'.join(lines)
|
||||
if data:
|
||||
conf.update(json.loads(data))
|
||||
|
||||
return conf
|
||||
@@ -0,0 +1,61 @@
|
||||
// This is the default config file and should NOT be changed.
|
||||
//
|
||||
// There are two ways to create a custom config.
|
||||
//
|
||||
// 1. You can create a "config.json" at repo/config/config.json.
|
||||
// This file will override any keys in this default.json file
|
||||
// and will be ignored by git.
|
||||
//
|
||||
// 2. The other way is to create an environment variable with the name
|
||||
// STUDIO_LIBRARY_CONFIG_PATH. The value of this variable should be the
|
||||
// full path to your config.json file.
|
||||
//
|
||||
// 3. Or you could use code to modify the config before loading the window.
|
||||
// import studiolibrary
|
||||
// studiolibrary.config.set("recursiveSearchDepth", 6)
|
||||
// studiolibrary.main()
|
||||
|
||||
{
|
||||
// The database path is used for caching the library items.
|
||||
// You can use environment variables within the path. eg: {HOME}
|
||||
"databasePath": "{root}/.studiolibrary/database.json",
|
||||
|
||||
// The temp location used for saving out items and thumbnails
|
||||
"tempPath": "{temp}/StudioLibrary/{user}",
|
||||
|
||||
// The metadata path used for each item. Used for tags, item color etc
|
||||
// eg: /library/data/animation/nemo/.metadata
|
||||
"metadataPath": "{path}/.studiolibrary/metadata.json",
|
||||
|
||||
// Used for saving persistent user data
|
||||
"settingsPath": "{local}/StudioLibrary/LibraryWidget.json",
|
||||
|
||||
// The maximum walking depth from the root directory
|
||||
"recursiveSearchDepth": 5,
|
||||
|
||||
// A list of paths to ignore when walking the root directory
|
||||
"ignorePaths": ["/."],
|
||||
|
||||
// The command used to show a path in the file explorer
|
||||
//"showInFolderCmd": "konqueror \"{path}\"&",
|
||||
|
||||
// Enables the scale factor option in the setting dialog
|
||||
// This might be useful when using high-DPI devices like a 4k monitor
|
||||
"scaleFactorEnabled": true,
|
||||
|
||||
// Check if there are any new versions available on start up
|
||||
"checkForUpdatesEnabled": true,
|
||||
|
||||
// A list of the default item plugins
|
||||
"itemRegistry": [
|
||||
// This is an example item for development
|
||||
// "studiolibrarymaya.exampleitem.ExampleItem",
|
||||
// The maya file item is in development
|
||||
// "studiolibrarymaya.mayafileitem.MayaFileItem",
|
||||
"studiolibrarymaya.poseitem.PoseItem",
|
||||
"studiolibrarymaya.animitem.AnimItem",
|
||||
"studiolibrarymaya.mirroritem.MirrorItem",
|
||||
"studiolibrarymaya.setsitem.SetsItem",
|
||||
"studiolibrary.folderitem.FolderItem"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from studiovendor.Qt import QtGui
|
||||
from studiovendor.Qt import QtCore
|
||||
from studiovendor.Qt import QtWidgets
|
||||
|
||||
import studioqt
|
||||
import studiolibrary
|
||||
import studiolibrary.widgets
|
||||
|
||||
|
||||
class FolderItem(studiolibrary.LibraryItem):
|
||||
|
||||
NAME = "Folder"
|
||||
TYPE = "Folder"
|
||||
|
||||
MENU_ORDER = 0 # Show at the top of the menu
|
||||
SYNC_ORDER = 100 # Last item to run when syncing
|
||||
|
||||
LOAD_WIDGET_CLASS = studiolibrary.widgets.PreviewWidget
|
||||
ENABLE_NESTED_ITEMS = True
|
||||
|
||||
ICON_PATH = studiolibrary.resource.get("icons", "folder.svg")
|
||||
TYPE_ICON_PATH = "" # Don't show a type icon for the folder item
|
||||
THUMBNAIL_PATH = studiolibrary.resource.get("icons", "folder_item.png")
|
||||
|
||||
DEFAULT_ICON_COLOR = "rgba(150,150,150,100)"
|
||||
|
||||
DEFAULT_ICON_COLORS = [
|
||||
"rgb(239, 112, 99)",
|
||||
"rgb(239, 207, 103)",
|
||||
"rgb(136, 200, 101)",
|
||||
"rgb(111, 183, 239)",
|
||||
"rgb(199, 142, 220)",
|
||||
DEFAULT_ICON_COLOR,
|
||||
]
|
||||
|
||||
DEFAULT_ICONS = [
|
||||
"folder.svg",
|
||||
"user.svg",
|
||||
"character.svg",
|
||||
"users.svg",
|
||||
"inbox.svg",
|
||||
"favorite.svg",
|
||||
"shot.svg",
|
||||
"asset.svg",
|
||||
"assets.svg",
|
||||
"cloud.svg",
|
||||
"book.svg",
|
||||
"archive.svg",
|
||||
"circle.svg",
|
||||
"share.svg",
|
||||
"tree.svg",
|
||||
"environment.svg",
|
||||
"vehicle.svg",
|
||||
"trash.svg",
|
||||
"layers.svg",
|
||||
"database.svg",
|
||||
"video.svg",
|
||||
"face.svg",
|
||||
"hand.svg",
|
||||
"globe.svg",
|
||||
]
|
||||
|
||||
_THUMBNAIL_ICON_CACHE = {}
|
||||
|
||||
@classmethod
|
||||
def match(cls, path):
|
||||
"""
|
||||
Return True if the given path is supported by the item.
|
||||
|
||||
:type path: str
|
||||
:rtype: bool
|
||||
"""
|
||||
if os.path.isdir(path):
|
||||
return True
|
||||
|
||||
def itemData(self):
|
||||
"""
|
||||
Reimplementing this method to set a trash folder icon.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
data = super(FolderItem, self).itemData()
|
||||
|
||||
if self.path().endswith("Trash") and not data.get("icon"):
|
||||
data["icon"] = "trash.svg"
|
||||
|
||||
return data
|
||||
|
||||
def updatePermissionsEnabled(self):
|
||||
return True
|
||||
|
||||
def updateMetadata(self, metadata):
|
||||
"""
|
||||
Overriding this method to support updating the library widget directly.
|
||||
|
||||
:type metadata: dict
|
||||
"""
|
||||
super(FolderItem, self).updateMetadata(metadata)
|
||||
|
||||
if self.libraryWindow():
|
||||
self.libraryWindow().setFolderData(self.path(), self.itemData())
|
||||
self.updateIcon()
|
||||
|
||||
def doubleClicked(self):
|
||||
"""Overriding this method to show the items contained in the folder."""
|
||||
self.libraryWindow().selectFolderPath(self.path())
|
||||
|
||||
def createOverwriteMenu(self, menu):
|
||||
"""
|
||||
Overwriting this method to ignore/hide the overwrite menu action.
|
||||
|
||||
:type menu: QtWidgets.QMenu
|
||||
"""
|
||||
pass
|
||||
|
||||
def _showPreviewFromMenu(self):
|
||||
|
||||
self.libraryWindow().itemsWidget().clearSelection()
|
||||
self.showPreviewWidget(self.libraryWindow())
|
||||
|
||||
def contextEditMenu(self, menu, items=None):
|
||||
"""
|
||||
Called when creating the context menu for the item.
|
||||
|
||||
:type menu: QtWidgets.QMenu
|
||||
:type items: list[FolderItem] or None
|
||||
"""
|
||||
super(FolderItem, self).contextEditMenu(menu, items=items)
|
||||
|
||||
action = QtWidgets.QAction("Show in Preview", menu)
|
||||
|
||||
action.triggered.connect(self._showPreviewFromMenu)
|
||||
menu.addAction(action)
|
||||
menu.addSeparator()
|
||||
|
||||
action = studiolibrary.widgets.colorpicker.ColorPickerAction(menu)
|
||||
action.picker().setColors(self.DEFAULT_ICON_COLORS)
|
||||
action.picker().colorChanged.connect(self.setIconColor)
|
||||
action.picker().setCurrentColor(self.iconColor())
|
||||
action.picker().menuButton().hide()
|
||||
menu.addAction(action)
|
||||
|
||||
iconName = self.itemData().get("icon", "")
|
||||
|
||||
action = studiolibrary.widgets.iconpicker.IconPickerAction(menu)
|
||||
action.picker().setIcons(self.DEFAULT_ICONS)
|
||||
action.picker().setCurrentIcon(iconName)
|
||||
action.picker().iconChanged.connect(self.setCustomIcon)
|
||||
action.picker().menuButton().hide()
|
||||
|
||||
menu.addAction(action)
|
||||
|
||||
def iconColor(self):
|
||||
"""
|
||||
Get the icon color for the folder item.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self.itemData().get("color", "")
|
||||
|
||||
def setIconColor(self, color):
|
||||
"""
|
||||
Set the icon color for the folder item.
|
||||
|
||||
:type color: str
|
||||
"""
|
||||
if color == self.DEFAULT_ICON_COLOR:
|
||||
color = ""
|
||||
|
||||
self.updateMetadata({"color": color})
|
||||
|
||||
def customIconPath(self):
|
||||
"""
|
||||
Get the icon for the folder item.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self.itemData().get("icon", "")
|
||||
|
||||
def setCustomIcon(self, name):
|
||||
"""
|
||||
Set the icon for the folder item.
|
||||
|
||||
:type name: str
|
||||
"""
|
||||
if name == "folder.svg":
|
||||
name = ""
|
||||
|
||||
self.updateMetadata({"icon": name})
|
||||
|
||||
def thumbnailIcon(self):
|
||||
"""
|
||||
Overriding this method add support for dynamic icon colors.
|
||||
|
||||
:rtype: QtGui.QIcon
|
||||
"""
|
||||
customPath = self.customIconPath()
|
||||
|
||||
if customPath and "/" not in customPath and "\\" not in customPath:
|
||||
customPath = studiolibrary.resource.get("icons", customPath)
|
||||
|
||||
color = self.iconColor()
|
||||
if not color:
|
||||
color = self.DEFAULT_ICON_COLOR
|
||||
|
||||
key = customPath + color
|
||||
icon = self._THUMBNAIL_ICON_CACHE.get(key)
|
||||
|
||||
if not icon:
|
||||
color1 = studioqt.Color.fromString(color)
|
||||
pixmap1 = studioqt.Pixmap(self.THUMBNAIL_PATH)
|
||||
pixmap1.setColor(color1)
|
||||
pixmap1 = pixmap1.scaled(
|
||||
128,
|
||||
128,
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation
|
||||
)
|
||||
|
||||
color2 = studioqt.Color.fromString("rgba(255,255,255,150)")
|
||||
pixmap2 = studioqt.Pixmap(customPath)
|
||||
pixmap2.setColor(color2)
|
||||
pixmap2 = pixmap2.scaled(
|
||||
64,
|
||||
64,
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation
|
||||
)
|
||||
|
||||
x = (128 - pixmap2.width()) / 2
|
||||
y = (128 - pixmap2.height()) / 2
|
||||
|
||||
painter = QtGui.QPainter(pixmap1)
|
||||
painter.drawPixmap(x, y+5, pixmap2)
|
||||
painter.end()
|
||||
|
||||
icon = studioqt.Icon(pixmap1)
|
||||
|
||||
self._THUMBNAIL_ICON_CACHE[key] = icon
|
||||
|
||||
return self._THUMBNAIL_ICON_CACHE.get(key)
|
||||
|
||||
def loadValidator(self, **kwargs):
|
||||
"""
|
||||
The validator used for validating the load arguments.
|
||||
|
||||
:type kwargs: dict
|
||||
"""
|
||||
if kwargs.get("fieldChanged") == "color":
|
||||
self.setIconColor(kwargs.get("color"))
|
||||
|
||||
if kwargs.get("fieldChanged") == "icon":
|
||||
self.setCustomIcon(kwargs.get("icon"))
|
||||
|
||||
def loadSchema(self):
|
||||
"""
|
||||
Get the info to display to user.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
created = os.stat(self.path()).st_ctime
|
||||
created = datetime.fromtimestamp(created).strftime("%Y-%m-%d %H:%M %p")
|
||||
|
||||
modified = os.stat(self.path()).st_mtime
|
||||
modified = datetime.fromtimestamp(modified).strftime("%Y-%m-%d %H:%M %p")
|
||||
|
||||
iconName = self.itemData().get("icon", "")
|
||||
|
||||
return [
|
||||
{
|
||||
"name": "infoGroup",
|
||||
"title": "Info",
|
||||
"value": True,
|
||||
"type": "group",
|
||||
"persistent": True,
|
||||
"persistentKey": "BaseItem",
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"value": self.name()
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"value": self.path()
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"value": created,
|
||||
},
|
||||
{
|
||||
"name": "modified",
|
||||
"value": modified,
|
||||
},
|
||||
{
|
||||
"name": "optionsGroup",
|
||||
"title": "Options",
|
||||
"type": "group",
|
||||
},
|
||||
|
||||
{
|
||||
"name": "color",
|
||||
"type": "color",
|
||||
"value": self.iconColor(),
|
||||
"layout": "vertical",
|
||||
"label": {"visible": False},
|
||||
"colors": self.DEFAULT_ICON_COLORS,
|
||||
},
|
||||
|
||||
{
|
||||
"name": "icon",
|
||||
"type": "iconPicker",
|
||||
"value": iconName,
|
||||
"layout": "vertical",
|
||||
"label": {"visible": False},
|
||||
"items": self.DEFAULT_ICONS,
|
||||
}
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def showSaveWidget(cls, libraryWindow):
|
||||
"""
|
||||
Show the dialog for creating a new folder.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
path = libraryWindow.selectedFolderPath() or libraryWindow.path()
|
||||
|
||||
name, button = studiolibrary.widgets.MessageBox.input(
|
||||
libraryWindow,
|
||||
"Create folder",
|
||||
"Create a new folder with the name:",
|
||||
buttons=[
|
||||
("Create", QtWidgets.QDialogButtonBox.AcceptRole),
|
||||
("Cancel", QtWidgets.QDialogButtonBox.RejectRole)
|
||||
]
|
||||
)
|
||||
|
||||
name = name.strip()
|
||||
|
||||
if name and button == "Create":
|
||||
path = os.path.join(path, name)
|
||||
|
||||
item = cls(path, libraryWindow=libraryWindow)
|
||||
item.safeSave()
|
||||
|
||||
if libraryWindow:
|
||||
libraryWindow.refresh()
|
||||
libraryWindow.selectFolderPath(path)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Adding this method to avoid NotImpementedError."""
|
||||
pass
|
||||
1081
2023/scripts/animation_tools/studiolibrary/studiolibrary/library.py
Normal file
@@ -0,0 +1,913 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
from studiovendor.Qt import QtGui
|
||||
from studiovendor.Qt import QtCore
|
||||
from studiovendor.Qt import QtWidgets
|
||||
|
||||
import studiolibrary
|
||||
import studiolibrary.widgets
|
||||
import studiolibrary.librarywindow
|
||||
|
||||
import studioqt
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ItemError(Exception):
|
||||
""""""
|
||||
|
||||
|
||||
class ItemSaveError(ItemError):
|
||||
""""""
|
||||
|
||||
|
||||
class ItemLoadError(ItemError):
|
||||
""""""
|
||||
|
||||
|
||||
class LibraryItemSignals(QtCore.QObject):
|
||||
""""""
|
||||
saved = QtCore.Signal(object)
|
||||
saving = QtCore.Signal(object)
|
||||
loaded = QtCore.Signal(object)
|
||||
copied = QtCore.Signal(object, object, object)
|
||||
deleted = QtCore.Signal(object)
|
||||
renamed = QtCore.Signal(object, object, object)
|
||||
dataChanged = QtCore.Signal(object)
|
||||
|
||||
# Note: We will be changing the base class in the near future
|
||||
class LibraryItem(studiolibrary.widgets.Item):
|
||||
|
||||
NAME = ""
|
||||
TYPE = ""
|
||||
THUMBNAIL_PATH = studiolibrary.resource.get("icons", "thumbnail.png")
|
||||
|
||||
EXTENSION = ""
|
||||
EXTENSIONS = []
|
||||
|
||||
ENABLE_DELETE = False
|
||||
ENABLE_NESTED_ITEMS = False
|
||||
|
||||
SYNC_ORDER = 10
|
||||
MENU_ORDER = 10
|
||||
|
||||
SAVE_WIDGET_CLASS = None
|
||||
LOAD_WIDGET_CLASS = None
|
||||
|
||||
_libraryItemSignals = LibraryItemSignals()
|
||||
|
||||
saved = _libraryItemSignals.saved
|
||||
saving = _libraryItemSignals.saving
|
||||
loaded = _libraryItemSignals.loaded
|
||||
copied = _libraryItemSignals.renamed
|
||||
renamed = _libraryItemSignals.renamed
|
||||
deleted = _libraryItemSignals.deleted
|
||||
dataChanged = _libraryItemSignals.dataChanged
|
||||
|
||||
def createItemData(self):
|
||||
"""
|
||||
Called when syncing the given path with the library cache.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
import studiolibrary.library
|
||||
|
||||
path = self.path()
|
||||
path = studiolibrary.latestVersionPath(path) or path
|
||||
|
||||
if studiolibrary.isVersionPath(path):
|
||||
dirname = os.path.dirname(path)
|
||||
name = os.path.basename(dirname)
|
||||
dirname, basename, extension = studiolibrary.splitPath(dirname)
|
||||
else:
|
||||
dirname, basename, extension = studiolibrary.splitPath(path)
|
||||
name = os.path.basename(path)
|
||||
|
||||
category = os.path.basename(dirname) or dirname
|
||||
modified = ""
|
||||
|
||||
if os.path.exists(path):
|
||||
modified = os.path.getmtime(path)
|
||||
|
||||
itemData = dict(self.readMetadata())
|
||||
|
||||
itemData.update({
|
||||
"name": name,
|
||||
"path": path,
|
||||
"type": self.TYPE or extension,
|
||||
"folder": dirname,
|
||||
"category": category,
|
||||
"modified": modified,
|
||||
"__class__": self.__class__.__module__ + "." + self.__class__.__name__
|
||||
})
|
||||
|
||||
return itemData
|
||||
|
||||
@classmethod
|
||||
def createAction(cls, menu, libraryWindow):
|
||||
"""
|
||||
Return the action to be displayed when the user
|
||||
ks the "plus" icon.
|
||||
|
||||
:type menu: QtWidgets.QMenu
|
||||
:type libraryWindow: studiolibrary.LibraryWindow
|
||||
:rtype: QtCore.QAction
|
||||
"""
|
||||
if cls.NAME:
|
||||
|
||||
icon = QtGui.QIcon(cls.ICON_PATH)
|
||||
callback = partial(cls.showSaveWidget, libraryWindow)
|
||||
|
||||
action = QtWidgets.QAction(icon, cls.NAME, menu)
|
||||
action.triggered.connect(callback)
|
||||
|
||||
return action
|
||||
|
||||
@classmethod
|
||||
def showSaveWidget(cls, libraryWindow, item=None):
|
||||
"""
|
||||
Show the create widget for creating a new item.
|
||||
|
||||
:type libraryWindow: studiolibrary.LibraryWindow
|
||||
:type item: studiolibrary.LibraryItem or None
|
||||
"""
|
||||
item = item or cls()
|
||||
widget = cls.SAVE_WIDGET_CLASS(item=item)
|
||||
libraryWindow.setCreateWidget(widget)
|
||||
|
||||
@classmethod
|
||||
def isValidPath(cls, path):
|
||||
"""
|
||||
This method has been deprecated.
|
||||
|
||||
Please use LibraryItem.match(cls, path)
|
||||
|
||||
:type path: str
|
||||
:rtype: bool
|
||||
"""
|
||||
return cls.match(path)
|
||||
|
||||
@classmethod
|
||||
def match(cls, path):
|
||||
"""
|
||||
Return True if the given path location is supported by the item.
|
||||
|
||||
:type path: str
|
||||
:rtype: bool
|
||||
"""
|
||||
extensions = cls.EXTENSIONS
|
||||
if not extensions and cls.EXTENSION:
|
||||
extensions = [cls.EXTENSION]
|
||||
|
||||
for ext in extensions:
|
||||
if path.endswith(ext):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path="",
|
||||
library=None,
|
||||
libraryWindow=None,
|
||||
):
|
||||
"""
|
||||
The LibraryItem class provides an item for use with the LibraryWindow.
|
||||
|
||||
:type path: str
|
||||
:type library: studiolibrary.Library or None
|
||||
:type libraryWindow: studiolibrary.LibraryWindow or None
|
||||
"""
|
||||
super(LibraryItem, self).__init__()
|
||||
|
||||
self._path = path
|
||||
self._modal = None
|
||||
self._library = None
|
||||
self._metadata = None
|
||||
self._libraryWindow = None
|
||||
|
||||
self._readOnly = False
|
||||
self._ignoreExistsDialog = False
|
||||
|
||||
if libraryWindow:
|
||||
self.setLibraryWindow(libraryWindow)
|
||||
|
||||
if library:
|
||||
self.setLibrary(library)
|
||||
|
||||
# if path:
|
||||
# self.setPath(path)
|
||||
|
||||
def updatePermissionsEnabled(self):
|
||||
return False
|
||||
|
||||
def setReadOnly(self, readOnly):
|
||||
"""
|
||||
Set the item to read only.
|
||||
|
||||
:type readOnly: bool
|
||||
"""
|
||||
self._readOnly = readOnly
|
||||
|
||||
def isReadOnly(self):
|
||||
"""
|
||||
Check if the item is read only.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
if self.isLocked():
|
||||
return True
|
||||
|
||||
return self._readOnly
|
||||
|
||||
def isLocked(self):
|
||||
"""
|
||||
Check if the item has been locked by the window.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
locked = False
|
||||
if self.libraryWindow():
|
||||
locked = self.libraryWindow().isLocked()
|
||||
return locked
|
||||
|
||||
def isDeletable(self):
|
||||
"""
|
||||
Check if the item is deletable.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
if self.isLocked():
|
||||
return False
|
||||
|
||||
return self.ENABLE_DELETE
|
||||
|
||||
def overwrite(self):
|
||||
"""
|
||||
Show the save widget with the input fields populated.
|
||||
"""
|
||||
self._ignoreExistsDialog = True
|
||||
widget = self.showSaveWidget(self.libraryWindow(), item=self)
|
||||
|
||||
def loadSchema(self):
|
||||
"""
|
||||
Get the options used to load the item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return []
|
||||
|
||||
def loadValidator(self, **kwargs):
|
||||
"""
|
||||
Validate the current load options.
|
||||
|
||||
:type kwargs: dict
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return []
|
||||
|
||||
def load(self, *args, **kwargs):
|
||||
"""
|
||||
Reimplement this method for loading item data.
|
||||
|
||||
:type args: list
|
||||
:type kwargs: dict
|
||||
"""
|
||||
raise NotImplementedError("The load method has not been implemented!")
|
||||
|
||||
def id(self):
|
||||
"""
|
||||
Return the unique id for the item.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self.path()
|
||||
|
||||
def showToastMessage(self, text):
|
||||
"""
|
||||
A convenience method for showing the toast widget with the given text.
|
||||
|
||||
:type text: str
|
||||
"""
|
||||
if self.libraryWindow():
|
||||
self.libraryWindow().showToastMessage(text)
|
||||
|
||||
def showErrorDialog(self, title, text):
|
||||
"""
|
||||
Convenience method for showing an error dialog to the user.
|
||||
|
||||
:type title: str
|
||||
:type text: str
|
||||
:rtype: QMessageBox.StandardButton or None
|
||||
"""
|
||||
if self.libraryWindow():
|
||||
self.libraryWindow().showErrorMessage(text)
|
||||
|
||||
button = None
|
||||
|
||||
if not self._modal:
|
||||
self._modal = True
|
||||
|
||||
try:
|
||||
button = studiolibrary.widgets.MessageBox.critical(self.libraryWindow(), title, text)
|
||||
finally:
|
||||
self._modal = False
|
||||
|
||||
return button
|
||||
|
||||
def showExceptionDialog(self, title, error):
|
||||
"""
|
||||
Convenience method for showing a question dialog to the user.
|
||||
|
||||
:type title: str
|
||||
:type error: Exception
|
||||
:rtype: QMessageBox.StandardButton
|
||||
"""
|
||||
logger.exception(error)
|
||||
return self.showErrorDialog(title, error)
|
||||
|
||||
def showQuestionDialog(self, title, text):
|
||||
"""
|
||||
Convenience method for showing a question dialog to the user.
|
||||
|
||||
:type title: str
|
||||
:type text: str
|
||||
:rtype: QMessageBox.StandardButton
|
||||
"""
|
||||
return studiolibrary.widgets.MessageBox.question(self.libraryWindow(), title, text)
|
||||
|
||||
def thumbnailPath(self):
|
||||
"""
|
||||
Return the thumbnail location on disc for this item.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
thumbnailPath = self.path() + "/thumbnail.jpg"
|
||||
if os.path.exists(thumbnailPath):
|
||||
return thumbnailPath
|
||||
|
||||
thumbnailPath = thumbnailPath.replace(".jpg", ".png")
|
||||
if os.path.exists(thumbnailPath):
|
||||
return thumbnailPath
|
||||
|
||||
return self.THUMBNAIL_PATH
|
||||
|
||||
def isTHUMBNAIL_PATH(self):
|
||||
"""
|
||||
Check if the thumbnail path is the default path.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
return self.thumbnailPath() == self.THUMBNAIL_PATH
|
||||
|
||||
def showPreviewWidget(self, libraryWindow):
|
||||
"""
|
||||
Show the preview Widget for the item instance.
|
||||
|
||||
:type libraryWindow: studiolibrary.LibraryWindow
|
||||
"""
|
||||
widget = self.previewWidget(libraryWindow)
|
||||
libraryWindow.setPreviewWidget(widget)
|
||||
|
||||
def previewWidget(self, libraryWindow):
|
||||
"""
|
||||
Return the widget to be shown when the user clicks on the item.
|
||||
|
||||
:type libraryWindow: studiolibrary.LibraryWindow
|
||||
:rtype: QtWidgets.QWidget or None
|
||||
"""
|
||||
widget = None
|
||||
|
||||
if self.LOAD_WIDGET_CLASS:
|
||||
widget = self.LOAD_WIDGET_CLASS(item=self)
|
||||
|
||||
return widget
|
||||
|
||||
def isVersionPath(self):
|
||||
return studiolibrary.isVersionPath(self.path())
|
||||
|
||||
def contextEditMenu(self, menu, items=None):
|
||||
"""
|
||||
Called when the user would like to edit the item from the menu.
|
||||
|
||||
The given menu is shown as a submenu of the main context menu.
|
||||
|
||||
:type menu: QtWidgets.QMenu
|
||||
:type items: list[LibraryItem] or None
|
||||
"""
|
||||
# Adding a blank icon fixes the text alignment issue when using Qt 5.12.+
|
||||
icon = studiolibrary.resource.icon("blank")
|
||||
enabled = not self.isVersionPath()
|
||||
|
||||
action = QtWidgets.QAction("Rename", menu)
|
||||
action.setEnabled(enabled)
|
||||
action.setIcon(icon)
|
||||
action.triggered.connect(self.showRenameDialog)
|
||||
menu.addAction(action)
|
||||
|
||||
action = QtWidgets.QAction("Move to", menu)
|
||||
action.setEnabled(enabled)
|
||||
action.triggered.connect(self.showMoveDialog)
|
||||
menu.addAction(action)
|
||||
|
||||
action = QtWidgets.QAction("Copy Path", menu)
|
||||
action.triggered.connect(self.copyPathToClipboard)
|
||||
action.setEnabled(enabled)
|
||||
menu.addAction(action)
|
||||
|
||||
if self.libraryWindow():
|
||||
action = QtWidgets.QAction("Select Folder", menu)
|
||||
action.triggered.connect(self.selectFolder)
|
||||
menu.addAction(action)
|
||||
|
||||
action = QtWidgets.QAction("Show in Folder", menu)
|
||||
action.triggered.connect(self.showInFolder)
|
||||
menu.addAction(action)
|
||||
|
||||
if self.isDeletable():
|
||||
menu.addSeparator()
|
||||
action = QtWidgets.QAction("Delete", menu)
|
||||
action.setEnabled(enabled)
|
||||
action.triggered.connect(self.showDeleteDialog)
|
||||
menu.addAction(action)
|
||||
|
||||
self.createOverwriteMenu(menu)
|
||||
|
||||
def createOverwriteMenu(self, menu):
|
||||
"""
|
||||
Create a menu or action to trigger the overwrite method.
|
||||
|
||||
:type menu: QtWidgets.QMenu
|
||||
"""
|
||||
if not self.isReadOnly():
|
||||
enabled = not self.isVersionPath()
|
||||
menu.addSeparator()
|
||||
action = QtWidgets.QAction("Overwrite", menu)
|
||||
action.setEnabled(enabled)
|
||||
action.triggered.connect(self.overwrite)
|
||||
menu.addAction(action)
|
||||
|
||||
def copyPathToClipboard(self):
|
||||
"""Copy the item path to the system clipboard."""
|
||||
cb = QtWidgets.QApplication.clipboard()
|
||||
cb.clear(mode=cb.Clipboard)
|
||||
cb.setText(self.path(), mode=cb.Clipboard)
|
||||
|
||||
def contextMenu(self, menu, items=None):
|
||||
"""
|
||||
Called when the user right clicks on the item.
|
||||
|
||||
:type menu: QtWidgets.QMenu
|
||||
:type items: list[LibraryItem]
|
||||
:rtype: None
|
||||
"""
|
||||
pass
|
||||
|
||||
def showInFolder(self):
|
||||
"""Open the file explorer at the given path location."""
|
||||
path = self.path()
|
||||
studiolibrary.showInFolder(path)
|
||||
|
||||
def selectFolder(self):
|
||||
"""select the folder in the library widget"""
|
||||
if self.libraryWindow():
|
||||
path = '/'.join(studiolibrary.normPath(self.path()).split('/')[:-1])
|
||||
self.libraryWindow().selectFolderPath(path)
|
||||
|
||||
def url(self):
|
||||
"""Used by the mime data when dragging/dropping the item."""
|
||||
return QtCore.QUrl("file:///" + self.path())
|
||||
|
||||
def setLibraryWindow(self, libraryWindow):
|
||||
"""
|
||||
Set the library widget containing the item.
|
||||
|
||||
:rtype: studiolibrary.LibraryWindow or None
|
||||
"""
|
||||
self._libraryWindow = libraryWindow
|
||||
|
||||
def libraryWindow(self):
|
||||
"""
|
||||
Return the library widget containing the item.
|
||||
|
||||
:rtype: studiolibrary.LibraryWindow or None
|
||||
"""
|
||||
return self._libraryWindow
|
||||
|
||||
def setLibrary(self, library):
|
||||
"""
|
||||
Set the library model for the item.
|
||||
|
||||
:type library: studiolibrary.Library
|
||||
"""
|
||||
self._library = library
|
||||
|
||||
def library(self):
|
||||
"""
|
||||
Return the library model for the item.
|
||||
|
||||
:rtype: studiolibrary.Library or None
|
||||
"""
|
||||
if not self._library and self.libraryWindow():
|
||||
return self.libraryWindow().library()
|
||||
|
||||
return self._library
|
||||
|
||||
def setIconPath(self, path):
|
||||
"""
|
||||
Set the icon path for the current item.
|
||||
|
||||
:type path: str
|
||||
:rtype: None
|
||||
"""
|
||||
self._iconPath = path
|
||||
|
||||
def iconPath(self):
|
||||
"""
|
||||
Return the icon path for the current item.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
return self._iconPath
|
||||
|
||||
def mimeText(self):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
return self.path()
|
||||
|
||||
def path(self):
|
||||
"""
|
||||
Get the path for the item.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self._path
|
||||
|
||||
def setPath(self, path):
|
||||
"""
|
||||
Set the path for the item.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
self._path = studiolibrary.normPath(path)
|
||||
|
||||
def setMetadata(self, metadata):
|
||||
"""
|
||||
Set the given metadata for the item.
|
||||
|
||||
:type metadata: dict
|
||||
"""
|
||||
self._metadata = metadata
|
||||
|
||||
def metadata(self):
|
||||
"""
|
||||
Get the metadata for the item from disc.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
return self._metadata
|
||||
|
||||
def updateMetadata(self, metadata):
|
||||
"""
|
||||
Update the current metadata from disc with the given metadata.
|
||||
|
||||
:type metadata: dict
|
||||
"""
|
||||
metadata_ = self.readMetadata()
|
||||
metadata_.update(metadata)
|
||||
self.saveMetadata(metadata_)
|
||||
|
||||
def saveMetadata(self, metadata):
|
||||
"""
|
||||
Save the given metadata to disc.
|
||||
|
||||
:type metadata: dict
|
||||
"""
|
||||
formatString = studiolibrary.config.get('metadataPath')
|
||||
path = studiolibrary.formatPath(formatString, self.path())
|
||||
studiolibrary.saveJson(path, metadata)
|
||||
|
||||
if self.updatePermissionsEnabled():
|
||||
self.library().updatePermissions(path)
|
||||
|
||||
self.setMetadata(metadata)
|
||||
self.syncItemData(emitDataChanged=False)
|
||||
self.dataChanged.emit(self)
|
||||
|
||||
def readMetadata(self):
|
||||
"""
|
||||
Read the metadata for the item from disc.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
if self._metadata is None:
|
||||
formatString = studiolibrary.config.get('metadataPath')
|
||||
path = studiolibrary.formatPath(formatString, self.path())
|
||||
|
||||
if os.path.exists(path):
|
||||
self._metadata = studiolibrary.readJson(path)
|
||||
else:
|
||||
self._metadata = {}
|
||||
|
||||
return self._metadata
|
||||
|
||||
def syncItemData(self, emitDataChanged=True):
|
||||
"""Sync the item data to the database."""
|
||||
data = self.createItemData()
|
||||
self.setItemData(data)
|
||||
|
||||
if self.library():
|
||||
self.library().saveItemData([self], emitDataChanged=emitDataChanged)
|
||||
|
||||
def saveSchema(self):
|
||||
"""
|
||||
Get the schema used for saving the item.
|
||||
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return []
|
||||
|
||||
def saveValidator(self, **fields):
|
||||
"""
|
||||
Validate the given save fields.
|
||||
|
||||
:type fields: dict
|
||||
:rtype: list[dict]
|
||||
"""
|
||||
return []
|
||||
|
||||
@studioqt.showWaitCursor
|
||||
def safeSave(self, *args, **kwargs):
|
||||
"""
|
||||
Safe save the item.
|
||||
"""
|
||||
dst = self.path()
|
||||
|
||||
if dst and not dst.endswith(self.EXTENSION):
|
||||
dst += self.EXTENSION
|
||||
|
||||
self.setPath(dst)
|
||||
|
||||
logger.debug(u'Item Saving: {0}'.format(dst))
|
||||
self.saving.emit(self)
|
||||
|
||||
if os.path.exists(dst):
|
||||
if studiolibrary.latestVersionPath(self.path()):
|
||||
raise NameError("You can only save items that were "
|
||||
"created using Studio Library version 2!")
|
||||
elif self._ignoreExistsDialog:
|
||||
self._moveToTrash()
|
||||
else:
|
||||
self.showAlreadyExistsDialog()
|
||||
|
||||
tmp = studiolibrary.createTempPath(self.__class__.__name__)
|
||||
|
||||
self.setPath(tmp)
|
||||
|
||||
self.save(*args, **kwargs)
|
||||
|
||||
shutil.move(tmp, dst)
|
||||
self.setPath(dst)
|
||||
|
||||
if self.updatePermissionsEnabled():
|
||||
self.library().updatePermissions(dst)
|
||||
|
||||
self.syncItemData()
|
||||
|
||||
if self.libraryWindow():
|
||||
self.libraryWindow().selectItems([self])
|
||||
|
||||
self.saved.emit(self)
|
||||
logger.debug(u'Item Saved: {0}'.format(dst))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Save the item io data to the given path.
|
||||
|
||||
:type args: list
|
||||
:type kwargs: dict
|
||||
"""
|
||||
raise NotImplementedError("The save method has not been implemented!")
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Support for copy and rename
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Delete the item from disc and the library model.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
studiolibrary.removePath(self.path())
|
||||
|
||||
if self.library():
|
||||
self.library().removePath(self.path())
|
||||
|
||||
self.deleted.emit(self)
|
||||
|
||||
def copy(self, dst):
|
||||
"""
|
||||
Make a copy/duplicate the current item to the given destination.
|
||||
|
||||
:type dst: str
|
||||
:rtype: None
|
||||
"""
|
||||
src = self.path()
|
||||
dst = studiolibrary.copyPath(src, dst)
|
||||
|
||||
if self.library():
|
||||
self.library().copyPath(src, dst)
|
||||
|
||||
self.copied.emit(self, src, dst)
|
||||
|
||||
if self.libraryWindow():
|
||||
self.libraryWindow().refresh()
|
||||
|
||||
def move(self, dst):
|
||||
"""
|
||||
Move the current item to the given destination.
|
||||
|
||||
:type dst: str
|
||||
:rtype: None
|
||||
"""
|
||||
self.rename(dst)
|
||||
|
||||
def rename(self, dst, extension=None):
|
||||
"""
|
||||
Rename the current path to the given destination path.
|
||||
|
||||
:type dst: str
|
||||
:type extension: bool or None
|
||||
:rtype: None
|
||||
"""
|
||||
extension = extension or self.EXTENSION
|
||||
if dst and extension not in dst:
|
||||
dst += extension
|
||||
|
||||
src = self.path()
|
||||
|
||||
# Rename the path on the filesystem
|
||||
dst = studiolibrary.renamePath(src, dst)
|
||||
|
||||
# Rename the path inside the library database
|
||||
if self.library():
|
||||
self.library().renamePath(src, dst)
|
||||
|
||||
self._path = dst
|
||||
|
||||
self.syncItemData()
|
||||
|
||||
self.renamed.emit(self, src, dst)
|
||||
|
||||
def showRenameDialog(self, parent=None):
|
||||
"""
|
||||
Show the rename dialog.
|
||||
|
||||
:type parent: QtWidgets.QWidget
|
||||
"""
|
||||
if self.isVersionPath():
|
||||
raise NameError("You can only rename items that were "
|
||||
"created using Studio Library version 2!")
|
||||
|
||||
select = False
|
||||
|
||||
if self.libraryWindow():
|
||||
parent = parent or self.libraryWindow()
|
||||
select = self.libraryWindow().selectedFolderPath() == self.path()
|
||||
|
||||
name, button = studiolibrary.widgets.MessageBox.input(
|
||||
parent,
|
||||
"Rename item",
|
||||
"Rename the current item to:",
|
||||
inputText=self.name(),
|
||||
buttons=[
|
||||
("Rename", QtWidgets.QDialogButtonBox.AcceptRole),
|
||||
("Cancel", QtWidgets.QDialogButtonBox.RejectRole)
|
||||
]
|
||||
)
|
||||
|
||||
if button == "Rename":
|
||||
try:
|
||||
self.rename(name)
|
||||
|
||||
if select:
|
||||
self.libraryWindow().selectFolderPath(self.path())
|
||||
|
||||
except Exception as error:
|
||||
self.showExceptionDialog("Rename Error", error)
|
||||
raise
|
||||
|
||||
return button
|
||||
|
||||
def showMoveDialog(self, parent=None):
|
||||
"""
|
||||
Show the move to browser dialog.
|
||||
|
||||
:type parent: QtWidgets.QWidget
|
||||
"""
|
||||
if self.isVersionPath():
|
||||
raise NameError("You can only move items that were "
|
||||
"created using Studio Library version 2!")
|
||||
|
||||
title = "Move To..."
|
||||
path = os.path.dirname(os.path.dirname(self.path()))
|
||||
|
||||
dst = QtWidgets.QFileDialog.getExistingDirectory(None, title, path)
|
||||
|
||||
if dst:
|
||||
dst = "{}/{}".format(dst, os.path.basename(self.path()))
|
||||
try:
|
||||
self.move(dst)
|
||||
except Exception as error:
|
||||
self.showExceptionDialog("Move Error", error)
|
||||
raise
|
||||
|
||||
def showDeleteDialog(self):
|
||||
"""
|
||||
Show the delete item dialog.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
if self.isVersionPath():
|
||||
raise NameError("You can only delete items that were "
|
||||
"created using Studio Library version 2!")
|
||||
|
||||
text = 'Are you sure you want to delete this item?'
|
||||
|
||||
button = self.showQuestionDialog("Delete Item", text)
|
||||
|
||||
if button == QtWidgets.QDialogButtonBox.Yes:
|
||||
try:
|
||||
self.delete()
|
||||
except Exception as error:
|
||||
self.showExceptionDialog("Delete Error", error)
|
||||
raise
|
||||
|
||||
def showAlreadyExistsDialog(self):
|
||||
"""
|
||||
Show a warning dialog if the item already exists on save.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
if self.isVersionPath():
|
||||
raise NameError("You can only override items that were "
|
||||
"created using Studio Library version 2!")
|
||||
|
||||
if not self.libraryWindow():
|
||||
raise ItemSaveError("Item already exists!")
|
||||
|
||||
title = "Item already exists"
|
||||
text = 'Would you like to move the existing item "{}" to the trash?'
|
||||
text = text.format(os.path.basename(self.path()))
|
||||
|
||||
buttons = [
|
||||
QtWidgets.QDialogButtonBox.Yes,
|
||||
QtWidgets.QDialogButtonBox.Cancel
|
||||
]
|
||||
|
||||
try:
|
||||
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ArrowCursor)
|
||||
button = self.libraryWindow().showQuestionDialog(title, text, buttons)
|
||||
finally:
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
|
||||
if button == QtWidgets.QDialogButtonBox.Yes:
|
||||
self._moveToTrash()
|
||||
else:
|
||||
raise ItemSaveError("You cannot save over an existing item.")
|
||||
|
||||
return button
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Support for painting the type icon
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
def _moveToTrash(self):
|
||||
"""
|
||||
Move the current item to the trash.
|
||||
|
||||
This method should only be used when saving.
|
||||
"""
|
||||
path = self.path()
|
||||
library = self.library()
|
||||
item = studiolibrary.LibraryItem(path, library=library)
|
||||
self.libraryWindow().moveItemsToTrash([item])
|
||||
# self.setPath(path)
|
||||
@@ -0,0 +1,58 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import studioqt
|
||||
import studiolibrary
|
||||
|
||||
|
||||
def main(*args, **kwargs):
|
||||
"""
|
||||
Convenience method for creating/showing a library widget instance.
|
||||
|
||||
return studiolibrary.LibraryWindow.instance(
|
||||
name="",
|
||||
path="",
|
||||
show=True,
|
||||
lock=False,
|
||||
superusers=None,
|
||||
lockRegExp=None,
|
||||
unlockRegExp=None
|
||||
)
|
||||
|
||||
:rtype: studiolibrary.LibraryWindow
|
||||
"""
|
||||
# Reload all Studio Library modules when Shift is pressed.
|
||||
# This is for developers to test their changes in a DCC application.
|
||||
if studioqt.isShiftModifier():
|
||||
import studiolibrary
|
||||
studiolibrary.reload()
|
||||
|
||||
# Register all the items from the config file.
|
||||
import studiolibrary
|
||||
studiolibrary.registerItems()
|
||||
|
||||
if studiolibrary.isMaya():
|
||||
from studiolibrarymaya import mayalibrarywindow
|
||||
libraryWindow = mayalibrarywindow.MayaLibraryWindow.instance(*args, **kwargs)
|
||||
else:
|
||||
from studiolibrary import librarywindow
|
||||
libraryWindow = librarywindow.LibraryWindow.instance(*args, **kwargs)
|
||||
|
||||
return libraryWindow
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Run the Studio Library in a QApplication instance
|
||||
import studioqt
|
||||
with studioqt.app():
|
||||
studiolibrary.main()
|
||||
@@ -0,0 +1,74 @@
|
||||
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version. This library is distributed in the
|
||||
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Lesser General Public License for more details.
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from studioqt import Icon
|
||||
from studioqt import Pixmap
|
||||
|
||||
from . import utils
|
||||
|
||||
|
||||
PATH = os.path.abspath(__file__)
|
||||
DIRNAME = os.path.dirname(PATH)
|
||||
RESOURCE_DIRNAME = os.path.join(DIRNAME, "resource")
|
||||
|
||||
|
||||
def get(*args):
|
||||
"""
|
||||
This is a convenience function for returning the resource path.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
path = os.path.join(RESOURCE_DIRNAME, *args)
|
||||
return utils.normPath(path)
|
||||
|
||||
|
||||
def icon(*args, **kwargs):
|
||||
"""
|
||||
Return an Icon object from the given resource name.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
path = get("icons", *args)
|
||||
return Icon(pixmap(path, **kwargs))
|
||||
|
||||
|
||||
def pixmap(name, scope="icons", extension="png", color=None):
|
||||
"""
|
||||
Return a Pixmap object from the given resource name.
|
||||
|
||||
:type name: str
|
||||
:type scope: str
|
||||
:type extension: str
|
||||
:type color: str
|
||||
:rtype: QtWidgets.QPixmap
|
||||
"""
|
||||
if name.endswith(".svg"):
|
||||
extension = ""
|
||||
|
||||
path = ""
|
||||
|
||||
if os.path.exists(name):
|
||||
path = name
|
||||
|
||||
elif extension:
|
||||
path = get(scope, name + "." + extension)
|
||||
if not os.path.exists(path):
|
||||
path = get(scope, name + ".svg")
|
||||
|
||||
p = Pixmap(path)
|
||||
|
||||
if color:
|
||||
p.setColor(color)
|
||||
|
||||
return p
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
After Width: | Height: | Size: 149 B |
|
After Width: | Height: | Size: 224 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M32 448c0 17.7 14.3 32 32 32h384c17.7 0 32-14.3 32-32V160H32v288zm160-212c0-6.6 5.4-12 12-12h104c6.6 0 12 5.4 12 12v8c0 6.6-5.4 12-12 12H204c-6.6 0-12-5.4-12-12v-8zM480 32H32C14.3 32 0 46.3 0 64v48c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16V64c0-17.7-14.3-32-32-32z"/></svg>
|
||||
|
After Width: | Height: | Size: 344 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M69.4 210.6C89.8 126.5 165.6 64 256 64c71.1 0 133.1 38.6 166.3 96H368c-8.8 0-16 7.2-16 16s7.2 16 16 16h80.7H464c8.8 0 16-7.2 16-16V80c0-8.8-7.2-16-16-16s-16 7.2-16 16v60.6C408.8 75.5 337.5 32 256 32C149.6 32 60.5 106.2 37.7 205.6C35.5 215.2 43.1 224 53 224c7.9 0 14.6-5.7 16.5-13.4zm373.2 90.9C422.2 385.5 346.4 448 256 448c-71.1 0-133.1-38.6-166.3-96h54.5c8.8 0 16-7.2 16-16s-7.2-16-16-16H63.3 48.2c-8.8 0-16 7.2-16 16v96c0 8.8 7.2 16 16 16s16-7.2 16-16V371.8C103.4 436.6 174.7 480 256 480c106.4 0 195.5-74.2 218.3-173.6c2.2-9.6-5.4-18.4-15.3-18.4c-7.9 0-14.6 5.7-16.5 13.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 823 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M239.1 6.3l-208 78c-18.7 7-31.1 25-31.1 45v225.1c0 18.2 10.3 34.8 26.5 42.9l208 104c13.5 6.8 29.4 6.8 42.9 0l208-104c16.3-8.1 26.5-24.8 26.5-42.9V129.3c0-20-12.4-37.9-31.1-44.9l-208-78C262 2.2 250 2.2 239.1 6.3zM256 68.4l192 72v1.1l-192 78-192-78v-1.1l192-72zm32 356V275.5l160-65v133.9l-160 80z"/></svg>
|
||||
|
After Width: | Height: | Size: 374 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M488.6 250.2L392 214V105.5c0-15-9.3-28.4-23.4-33.7l-100-37.5c-8.1-3.1-17.1-3.1-25.3 0l-100 37.5c-14.1 5.3-23.4 18.7-23.4 33.7V214l-96.6 36.2C9.3 255.5 0 268.9 0 283.9V394c0 13.6 7.7 26.1 19.9 32.2l100 50c10.1 5.1 22.1 5.1 32.2 0l103.9-52 103.9 52c10.1 5.1 22.1 5.1 32.2 0l100-50c12.2-6.1 19.9-18.6 19.9-32.2V283.9c0-15-9.3-28.4-23.4-33.7zM358 214.8l-85 31.9v-68.2l85-37v73.3zM154 104.1l102-38.2 102 38.2v.6l-102 41.4-102-41.4v-.6zm84 291.1l-85 42.5v-79.1l85-38.8v75.4zm0-112l-102 41.4-102-41.4v-.6l102-38.2 102 38.2v.6zm240 112l-85 42.5v-79.1l85-38.8v75.4zm0-112l-102 41.4-102-41.4v-.6l102-38.2 102 38.2v.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 687 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 80c0-8.8 7.2-16 16-16H432c8.8 0 16 7.2 16 16s-7.2 16-16 16H16C7.2 96 0 88.8 0 80zM0 240c0-8.8 7.2-16 16-16H432c8.8 0 16 7.2 16 16s-7.2 16-16 16H16c-8.8 0-16-7.2-16-16zM448 400c0 8.8-7.2 16-16 16H16c-8.8 0-16-7.2-16-16s7.2-16 16-16H432c8.8 0 16 7.2 16 16z"/></svg>
|
||||
|
After Width: | Height: | Size: 505 B |
|
After Width: | Height: | Size: 570 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M448 358.4V25.6c0-16-9.6-25.6-25.6-25.6H96C41.6 0 0 41.6 0 96v320c0 54.4 41.6 96 96 96h326.4c12.8 0 25.6-9.6 25.6-25.6v-16c0-6.4-3.2-12.8-9.6-19.2-3.2-16-3.2-60.8 0-73.6 6.4-3.2 9.6-9.6 9.6-19.2zM272 160l26.66 53.33L352 240l-53.34 26.67L272 320l-26.66-53.33L192 240l53.34-26.67L272 160zM160 96l16-32 16 32 32 16-32 16-16 32-16-32-32-16 32-16zm220.8 352H96c-19.2 0-32-12.8-32-32s16-32 32-32h284.8v64z"/></svg>
|
||||
|
After Width: | Height: | Size: 479 B |
|
After Width: | Height: | Size: 318 B |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 324 B |
|
After Width: | Height: | Size: 705 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M512 144v288c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48h88l12.3-32.9c7-18.7 24.9-31.1 44.9-31.1h125.5c20 0 37.9 12.4 44.9 31.1L376 96h88c26.5 0 48 21.5 48 48zM376 288c0-66.2-53.8-120-120-120s-120 53.8-120 120 53.8 120 120 120 120-53.8 120-120zm-32 0c0 48.5-39.5 88-88 88s-88-39.5-88-88 39.5-88 88-88 88 39.5 88 88zm-120 0c0-17.6 14.4-32 32-32 8.8 0 16-7.2 16-16s-7.2-16-16-16c-35.3 0-64 28.7-64 64 0 8.8 7.2 16 16 16s16-7.2 16-16z"/></svg>
|
||||
|
After Width: | Height: | Size: 538 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M512 144v288c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48h88l12.3-32.9c7-18.7 24.9-31.1 44.9-31.1h125.5c20 0 37.9 12.4 44.9 31.1L376 96h88c26.5 0 48 21.5 48 48zM376 288c0-66.2-53.8-120-120-120s-120 53.8-120 120 53.8 120 120 120 120-53.8 120-120zm-32 0c0 48.5-39.5 88-88 88s-88-39.5-88-88 39.5-88 88-88 88 39.5 88 88z"/></svg>
|
||||
|
After Width: | Height: | Size: 422 B |
|
After Width: | Height: | Size: 963 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"/></svg>
|
||||
|
After Width: | Height: | Size: 199 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 512"><path d="M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"/></svg>
|
||||
|
After Width: | Height: | Size: 232 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M64 224h13.5c24.7 56.5 80.9 96 146.5 96s121.8-39.5 146.5-96H384c8.8 0 16-7.2 16-16v-96c0-8.8-7.2-16-16-16h-13.5C345.8 39.5 289.6 0 224 0S102.2 39.5 77.5 96H64c-8.8 0-16 7.2-16 16v96c0 8.8 7.2 16 16 16zm40-88c0-22.1 21.5-40 48-40h144c26.5 0 48 17.9 48 40v24c0 53-43 96-96 96h-48c-53 0-96-43-96-96v-24zm72 72l12-36 36-12-36-12-12-36-12 36-36 12 36 12 12 36zm151.6 113.4C297.7 340.7 262.2 352 224 352s-73.7-11.3-103.6-30.6C52.9 328.5 0 385 0 454.4v9.6c0 26.5 21.5 48 48 48h80v-64c0-17.7 14.3-32 32-32h128c17.7 0 32 14.3 32 32v64h80c26.5 0 48-21.5 48-48v-9.6c0-69.4-52.9-125.9-120.4-133zM272 448c-8.8 0-16 7.2-16 16s7.2 16 16 16 16-7.2 16-16-7.2-16-16-16zm-96 0c-8.8 0-16 7.2-16 16v48h32v-48c0-8.8-7.2-16-16-16z"/></svg>
|
||||
|
After Width: | Height: | Size: 787 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 766 B |
|
After Width: | Height: | Size: 848 B |
|
After Width: | Height: | Size: 816 B |
|
After Width: | Height: | Size: 943 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"/></svg>
|
||||
|
After Width: | Height: | Size: 149 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 334 B |
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M448 64V448H576c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32H448zm-32 0H224V448H416V64zM192 448V64H64C46.3 64 32 78.3 32 96V416c0 17.7 14.3 32 32 32H192zM0 96C0 60.7 28.7 32 64 32H576c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96z"/></svg>
|
||||
|
After Width: | Height: | Size: 506 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 284 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M448 73.143v45.714C448 159.143 347.667 192 224 192S0 159.143 0 118.857V73.143C0 32.857 100.333 0 224 0s224 32.857 224 73.143zM448 176v102.857C448 319.143 347.667 352 224 352S0 319.143 0 278.857V176c48.125 33.143 136.208 48.572 224 48.572S399.874 209.143 448 176zm0 160v102.857C448 479.143 347.667 512 224 512S0 479.143 0 438.857V336c48.125 33.143 136.208 48.572 224 48.572S399.874 369.143 448 336z"/></svg>
|
||||
|
After Width: | Height: | Size: 477 B |
|
After Width: | Height: | Size: 154 B |
|
After Width: | Height: | Size: 337 B |
|
After Width: | Height: | Size: 320 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M635.73 406.91l-194.04-297.6c-11.57-17.75-39.8-17.75-51.37 0l-32.84 50.37 67.68 105.68c2.38 3.72 1.3 8.67-2.42 11.05l-13.46 8.62c-3.72 2.38-8.67 1.3-11.05-2.42l-59.9-93.54-70.81-110.55c-12.4-19.36-42.64-19.36-55.04 0L4.58 403.18C-7.99 422.81 6.81 448 30.92 448h580.22c22.5 0 36.32-23.09 24.59-41.09z"/></svg>
|
||||
|
After Width: | Height: | Size: 379 B |
|
After Width: | Height: | Size: 207 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 180V56c0-13.3 10.7-24 24-24h124c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H64v84c0 6.6-5.4 12-12 12H12c-6.6 0-12-5.4-12-12zM288 44v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12V56c0-13.3-10.7-24-24-24H300c-6.6 0-12 5.4-12 12zm148 276h-40c-6.6 0-12 5.4-12 12v84h-84c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24V332c0-6.6-5.4-12-12-12zM160 468v-40c0-6.6-5.4-12-12-12H64v-84c0-6.6-5.4-12-12-12H12c-6.6 0-12 5.4-12 12v124c0 13.3 10.7 24 24 24h124c6.6 0 12-5.4 12-12z"/></svg>
|
||||
|
After Width: | Height: | Size: 588 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M25.9 3.4C19-2 8.9-.8 3.4 6.1S-.8 23.1 6.1 28.6l608 480c6.9 5.5 17 4.3 22.5-2.6s4.3-17-2.6-22.5L25.9 3.4zM605.5 268.3c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-51.2 0-96 14.8-133.9 36.8l27.3 21.5C244.6 74.2 280.2 64 320 64c70.4 0 127.7 32 170.8 72c43.1 40 71.9 88 85.2 120c-9.2 22.1-25.9 52-49.5 81.5l25.1 19.8c25.6-32 43.7-64.4 53.9-89zM88.4 154.7c-25.6 32-43.7 64.4-53.9 89c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c51.2 0 96-14.8 133.9-36.8l-27.3-21.5C395.4 437.8 359.8 448 320 448c-70.4 0-127.7-32-170.8-72C106.1 336 77.3 288 64 256c9.2-22.1 25.9-52 49.5-81.5L88.4 154.7zM320 384c16.7 0 32.7-3.2 47.4-9.1l-30.9-24.4c-5.4 .9-10.9 1.4-16.5 1.4c-51 0-92.8-39.8-95.8-90.1l-30.9-24.4c-.9 6-1.3 12.2-1.3 18.5c0 70.7 57.3 128 128 128zM448 256c0-70.7-57.3-128-128-128c-16.7 0-32.7 3.2-47.4 9.1l30.9 24.4c5.4-.9 10.9-1.4 16.5-1.4c51 0 92.8 39.8 95.8 90.1l30.9 24.4c.9-6 1.3-12.2 1.3-18.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M117.2 136C160.3 96 217.6 64 288 64s127.7 32 170.8 72c43.1 40 71.9 88 85.2 120c-13.3 32-42.1 80-85.2 120c-43.1 40-100.4 72-170.8 72s-127.7-32-170.8-72C74.1 336 45.3 288 32 256c13.3-32 42.1-80 85.2-120zM288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM192 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 825 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm80 168c17.7 0 32 14.3 32 32s-14.3 32-32 32-32-14.3-32-32 14.3-32 32-32zm-160 0c17.7 0 32 14.3 32 32s-14.3 32-32 32-32-14.3-32-32 14.3-32 32-32zm194.8 170.2C334.3 380.4 292.5 400 248 400s-86.3-19.6-114.8-53.8c-13.6-16.3 11-36.7 24.6-20.5 22.4 26.9 55.2 42.2 90.2 42.2s67.8-15.4 90.2-42.2c13.4-16.2 38.1 4.2 24.6 20.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 466 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 287 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm64 236c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-64c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-72v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm96-114.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 557 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm160-14.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 267 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 71.5C0 49.7 17.7 32 39.5 32H472.5C494.3 32 512 49.7 512 71.5c0 9.2-3.2 18.1-9.1 25.2L320 317.8V446.1c0 18.7-15.2 33.9-33.9 33.9c-7.5 0-14.8-2.5-20.8-7.1l-61-47.4c-7.8-6.1-12.4-15.4-12.4-25.3V317.8L9.1 96.7C3.2 89.6 0 80.7 0 71.5zM39.5 64c-4.2 0-7.5 3.4-7.5 7.5c0 1.8 .6 3.4 1.7 4.8L220.3 301.8c2.4 2.9 3.7 6.5 3.7 10.2v88.2l61 47.4c.3 .3 .7 .4 1.1 .4c1 0 1.9-.8 1.9-1.9V312c0-3.7 1.3-7.3 3.7-10.2L478.3 76.3c1.1-1.3 1.7-3 1.7-4.8c0-4.2-3.4-7.5-7.5-7.5H39.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 708 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M464 128H272l-64-64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V176c0-26.51-21.49-48-48-48z"/></svg>
|
||||
|
After Width: | Height: | Size: 207 B |
|
After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M572.694 292.093L500.27 416.248A63.997 63.997 0 0 1 444.989 448H45.025c-18.523 0-30.064-20.093-20.731-36.093l72.424-124.155A64 64 0 0 1 152 256h399.964c18.523 0 30.064 20.093 20.73 36.093zM152 224h328v-48c0-26.51-21.49-48-48-48H272l-64-64H48C21.49 64 0 85.49 0 112v278.046l69.077-118.418C86.214 242.25 117.989 224 152 224z"/></svg>
|
||||
|
After Width: | Height: | Size: 402 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111.03 8 0 119.03 0 256s111.03 248 248 248 248-111.03 248-248S384.97 8 248 8zm82.29 357.6c-3.9 3.88-7.99 7.95-11.31 11.28-2.99 3-5.1 6.7-6.17 10.71-1.51 5.66-2.73 11.38-4.77 16.87l-17.39 46.85c-13.76 3-28 4.69-42.65 4.69v-27.38c1.69-12.62-7.64-36.26-22.63-51.25-6-6-9.37-14.14-9.37-22.63v-32.01c0-11.64-6.27-22.34-16.46-27.97-14.37-7.95-34.81-19.06-48.81-26.11-11.48-5.78-22.1-13.14-31.65-21.75l-.8-.72a114.792 114.792 0 0 1-18.06-20.74c-9.38-13.77-24.66-36.42-34.59-51.14 20.47-45.5 57.36-82.04 103.2-101.89l24.01 12.01C203.48 89.74 216 82.01 216 70.11v-11.3c7.99-1.29 16.12-2.11 24.39-2.42l28.3 28.3c6.25 6.25 6.25 16.38 0 22.63L264 112l-10.34 10.34c-3.12 3.12-3.12 8.19 0 11.31l4.69 4.69c3.12 3.12 3.12 8.19 0 11.31l-8 8a8.008 8.008 0 0 1-5.66 2.34h-8.99c-2.08 0-4.08.81-5.58 2.27l-9.92 9.65a8.008 8.008 0 0 0-1.58 9.31l15.59 31.19c2.66 5.32-1.21 11.58-7.15 11.58h-5.64c-1.93 0-3.79-.7-5.24-1.96l-9.28-8.06a16.017 16.017 0 0 0-15.55-3.1l-31.17 10.39a11.95 11.95 0 0 0-8.17 11.34c0 4.53 2.56 8.66 6.61 10.69l11.08 5.54c9.41 4.71 19.79 7.16 30.31 7.16s22.59 27.29 32 32h66.75c8.49 0 16.62 3.37 22.63 9.37l13.69 13.69a30.503 30.503 0 0 1 8.93 21.57 46.536 46.536 0 0 1-13.72 32.98zM417 274.25c-5.79-1.45-10.84-5-14.15-9.97l-17.98-26.97a23.97 23.97 0 0 1 0-26.62l19.59-29.38c2.32-3.47 5.5-6.29 9.24-8.15l12.98-6.49C440.2 193.59 448 223.87 448 256c0 8.67-.74 17.16-1.82 25.54L417 274.25z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 479 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M408 216c-22.092 0-40 17.909-40 40h-8v-32c0-22.091-17.908-40-40-40s-40 17.909-40 40v32h-8V48c0-26.51-21.49-48-48-48s-48 21.49-48 48v208h-13.572L92.688 78.449C82.994 53.774 55.134 41.63 30.461 51.324 5.787 61.017-6.356 88.877 3.337 113.551l74.765 190.342-31.09 24.872c-15.381 12.306-19.515 33.978-9.741 51.081l64 112A39.998 39.998 0 0 0 136 512h240c18.562 0 34.686-12.77 38.937-30.838l32-136A39.97 39.97 0 0 0 448 336v-80c0-22.091-17.908-40-40-40z"/></svg>
|
||||
|
After Width: | Height: | Size: 526 B |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 870 B |
|
After Width: | Height: | Size: 548 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M567.938 243.908L462.25 85.374A48.003 48.003 0 0 0 422.311 64H153.689a48 48 0 0 0-39.938 21.374L8.062 243.908A47.994 47.994 0 0 0 0 270.533V400c0 26.51 21.49 48 48 48h480c26.51 0 48-21.49 48-48V270.533a47.994 47.994 0 0 0-8.062-26.625zM162.252 128h251.497l85.333 128H376l-32 64H232l-32-64H76.918l85.334-128z"/></svg>
|
||||
|
After Width: | Height: | Size: 387 B |
|
After Width: | Height: | Size: 207 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M12.41 148.02l232.94 105.67c6.8 3.09 14.49 3.09 21.29 0l232.94-105.67c16.55-7.51 16.55-32.52 0-40.03L266.65 2.31a25.607 25.607 0 0 0-21.29 0L12.41 107.98c-16.55 7.51-16.55 32.53 0 40.04zm487.18 88.28l-58.09-26.33-161.64 73.27c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.51 209.97l-58.1 26.33c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 276.3c16.55-7.5 16.55-32.5 0-40zm0 127.8l-57.87-26.23-161.86 73.37c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.29 337.87 12.41 364.1c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 404.1c16.55-7.5 16.55-32.5 0-40z"/></svg>
|
||||
|
After Width: | Height: | Size: 719 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"/></svg>
|
||||
|
After Width: | Height: | Size: 292 B |
|
After Width: | Height: | Size: 870 B |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M384 208A176 176 0 1 0 32 208a176 176 0 1 0 352 0zM343.3 366C307 397.2 259.7 416 208 416C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208c0 51.7-18.8 99-50 135.3L507.3 484.7c6.2 6.2 6.2 16.4 0 22.6s-16.4 6.2-22.6 0L343.3 366z"/></svg>
|
||||
|
After Width: | Height: | Size: 477 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M272 16c0-8.8-7.2-16-16-16s-16 7.2-16 16l0 224L16 240c-8.8 0-16 7.2-16 16s7.2 16 16 16l224 0 0 224c0 8.8 7.2 16 16 16s16-7.2 16-16l0-224 224 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-224 0 0-224z"/></svg>
|
||||
|
After Width: | Height: | Size: 437 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M240 64c0-8.8-7.2-16-16-16s-16 7.2-16 16V240H32c-8.8 0-16 7.2-16 16s7.2 16 16 16H208V448c0 8.8 7.2 16 16 16s16-7.2 16-16V272H416c8.8 0 16-7.2 16-16s-7.2-16-16-16H240V64z"/></svg>
|
||||
|
After Width: | Height: | Size: 417 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 923 B |
|
After Width: | Height: | Size: 943 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 549 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M508.5 468.9L387.1 347.5c-2.3-2.3-5.3-3.5-8.5-3.5h-13.2c31.5-36.5 50.6-84 50.6-136C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c52 0 99.5-19.1 136-50.6v13.2c0 3.2 1.3 6.2 3.5 8.5l121.4 121.4c4.7 4.7 12.3 4.7 17 0l22.6-22.6c4.7-4.7 4.7-12.3 0-17zM208 368c-88.4 0-160-71.6-160-160S119.6 48 208 48s160 71.6 160 160-71.6 160-160 160z"/></svg>
|
||||
|
After Width: | Height: | Size: 417 B |
|
After Width: | Height: | Size: 132 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M352 320c-22.608 0-43.387 7.819-59.79 20.895l-102.486-64.054a96.551 96.551 0 0 0 0-41.683l102.486-64.054C308.613 184.181 329.392 192 352 192c53.019 0 96-42.981 96-96S405.019 0 352 0s-96 42.981-96 96c0 7.158.79 14.13 2.276 20.841L155.79 180.895C139.387 167.819 118.608 160 96 160c-53.019 0-96 42.981-96 96s42.981 96 96 96c22.608 0 43.387-7.819 59.79-20.895l102.486 64.054A96.301 96.301 0 0 0 256 416c0 53.019 42.981 96 96 96s96-42.981 96-96-42.981-96-96-96z"/></svg>
|
||||
|
After Width: | Height: | Size: 536 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M488 64h-8v20c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12V64H96v20c0 6.6-5.4 12-12 12H44c-6.6 0-12-5.4-12-12V64h-8C10.7 64 0 74.7 0 88v336c0 13.3 10.7 24 24 24h8v-20c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v20h320v-20c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v20h8c13.3 0 24-10.7 24-24V88c0-13.3-10.7-24-24-24zM96 372c0 6.6-5.4 12-12 12H44c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40zm0-96c0 6.6-5.4 12-12 12H44c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40zm0-96c0 6.6-5.4 12-12 12H44c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40zm384 192c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40zm0-96c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40zm0-96c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40z"/></svg>
|
||||
|
After Width: | Height: | Size: 972 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 416c0 8.8 7.2 16 16 16l65.6 0c7.4 36.5 39.7 64 78.4 64s71-27.5 78.4-64L496 432c8.8 0 16-7.2 16-16s-7.2-16-16-16l-257.6 0c-7.4-36.5-39.7-64-78.4-64s-71 27.5-78.4 64L16 400c-8.8 0-16 7.2-16 16zm112 0a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zM304 256a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm48-80c-38.7 0-71 27.5-78.4 64L16 240c-8.8 0-16 7.2-16 16s7.2 16 16 16l257.6 0c7.4 36.5 39.7 64 78.4 64s71-27.5 78.4-64l65.6 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-65.6 0c-7.4-36.5-39.7-64-78.4-64zM192 144a48 48 0 1 1 0-96 48 48 0 1 1 0 96zm78.4-64C263 43.5 230.7 16 192 16s-71 27.5-78.4 64L16 80C7.2 80 0 87.2 0 96s7.2 16 16 16l97.6 0c7.4 36.5 39.7 64 78.4 64s71-27.5 78.4-64L496 112c8.8 0 16-7.2 16-16s-7.2-16-16-16L270.4 80z"/></svg>
|
||||
|
After Width: | Height: | Size: 952 B |
|
After Width: | Height: | Size: 358 B |