remove presets v1.0

This commit is contained in:
arabcoders 2025-04-01 20:05:35 +03:00
parent ff2139261b
commit fa9c084397
8 changed files with 112 additions and 237 deletions

View file

@ -392,7 +392,7 @@ class DownloadQueue(metaclass=Singleton):
{ "status": "text" } { "status": "text" }
""" """
_preset = Presets.get_instance().get(name=preset) _preset = Presets.get_instance().get(preset)
config = config if config else {} config = config if config else {}
if cli: if cli:

View file

@ -501,7 +501,7 @@ class HttpAPI(Common):
preset = request.query.get("preset") preset = request.query.get("preset")
if preset: if preset:
exists = Presets.get_instance().get(name=preset) exists = Presets.get_instance().get(preset)
if not exists: if not exists:
return web.json_response( return web.json_response(
data={"status": False, "message": f"Preset '{preset}' does not exist."}, data={"status": False, "message": f"Preset '{preset}' does not exist."},
@ -674,7 +674,7 @@ class HttpAPI(Common):
preset = request.query.get("preset") preset = request.query.get("preset")
if preset: if preset:
exists = Presets.get_instance().get(name=preset) exists = Presets.get_instance().get(preset)
if not exists: if not exists:
return web.json_response( return web.json_response(
data={"status": False, "message": f"Preset '{preset}' does not exist."}, data={"status": False, "message": f"Preset '{preset}' does not exist."},
@ -790,18 +790,6 @@ class HttpAPI(Common):
if not item.get("id", None) or not validate_uuid(item.get("id"), version=4): if not item.get("id", None) or not validate_uuid(item.get("id"), version=4):
item["id"] = str(uuid.uuid4()) item["id"] = str(uuid.uuid4())
if not item.get("args", None) or str(item.get("args")).strip() == "":
item["args"] = {}
if item.get("args", None) and isinstance(item.get("args"), str):
item["args"] = json.loads(item.get("args"))
if not item.get("postprocessors", None) or str(item.get("postprocessors")).strip() == "":
item["postprocessors"] = []
if item.get("postprocessors", None) and isinstance(item.get("postprocessors"), str):
item["postprocessors"] = json.loads(item.get("postprocessors"))
try: try:
cls.validate(item) cls.validate(item)
except ValueError as e: except ValueError as e:

View file

@ -11,6 +11,7 @@ from .config import Config
from .encoder import Encoder from .encoder import Encoder
from .Events import EventBus, Events from .Events import EventBus, Events
from .Singleton import Singleton from .Singleton import Singleton
from .Utils import arg_converter, clean_item
LOG = logging.getLogger("presets") LOG = logging.getLogger("presets")
@ -26,12 +27,6 @@ class Preset:
format: str format: str
"""The format of the preset.""" """The format of the preset."""
args: dict[str, list[str] | bool] = field(default_factory=dict)
"""The arguments of the preset."""
postprocessors: list = field(default_factory=list)
"""The postprocessors of the preset."""
folder: str = "" folder: str = ""
"""The default download folder to use if non is given.""" """The default download folder to use if non is given."""
@ -83,13 +78,19 @@ class Presets(metaclass=Singleton):
pass pass
with open(os.path.join(os.path.dirname(__file__), "presets.json")) as f: with open(os.path.join(os.path.dirname(__file__), "presets.json")) as f:
self._default_presets = [Preset(**preset) for preset in json.load(f)] for i, preset in enumerate(json.load(f)):
try:
self.validate(preset)
self._default_presets.append(Preset(**preset))
except Exception as e:
LOG.error(f"Failed to parse default preset '{i}'. '{e!s}'.")
continue
EventBus.get_instance().subscribe( def event_handler(_, __):
Events.PRESETS_ADD, msg = "Not implemented"
lambda data, _, **kwargs: self.add(**data.data), # noqa: ARG005 raise Exception(msg)
f"{__class__.__name__}.save",
) EventBus.get_instance().subscribe(Events.PRESETS_ADD, event_handler, f"{__class__.__name__}.save")
@staticmethod @staticmethod
def get_instance() -> "Presets": def get_instance() -> "Presets":
@ -150,22 +151,27 @@ class Presets(metaclass=Singleton):
LOG.info(f"No presets were defined in '{self._file}'.") LOG.info(f"No presets were defined in '{self._file}'.")
return self return self
needSaving = False need_save = False
for i, preset in enumerate(presets): for i, preset in enumerate(presets):
try: try:
if "id" not in preset: if "id" not in preset:
preset["id"] = str(uuid.uuid4()) preset["id"] = str(uuid.uuid4())
needSaving = True need_save = True
preset, preset_status = clean_item(preset, keys=("args", "postprocessors"))
preset = Preset(**preset) preset = Preset(**preset)
if preset_status:
need_save = True
self._presets.append(preset) self._presets.append(preset)
except Exception as e: except Exception as e:
LOG.error(f"Failed to parse preset at list position '{i}'. '{e!s}'.") LOG.error(f"Failed to parse preset at list position '{i}'. '{e!s}'.")
continue continue
if needSaving: if need_save:
LOG.info("Saving presets due to missing ids.") LOG.info("Saving presets due to format, or id change.")
self.save(self._presets) self.save(self._presets)
return self return self
@ -198,7 +204,7 @@ class Presets(metaclass=Singleton):
""" """
if not isinstance(preset, dict): if not isinstance(preset, dict):
if not isinstance(preset, Preset): if not isinstance(preset, Preset):
msg = "Invalid preset type." msg = f"Invalid preset type. Was expecting a (Preset|dict), but got '{type(preset).__name__}'."
raise ValueError(msg) # noqa: TRY004 raise ValueError(msg) # noqa: TRY004
preset = preset.serialize() preset = preset.serialize()
@ -215,18 +221,8 @@ class Presets(metaclass=Singleton):
msg = "No format found." msg = "No format found."
raise ValueError(msg) raise ValueError(msg)
if preset.get("args") and not isinstance(preset.get("args"), dict):
msg = "Invalid args type. expected dict."
raise ValueError(msg)
if preset.get("postprocessors") and not isinstance(preset.get("postprocessors"), list):
msg = "Invalid postprocessors type. expected list."
raise ValueError(msg)
if preset.get("cli"): if preset.get("cli"):
try: try:
from .Utils import arg_converter
arg_converter(args=preset.get("cli")) arg_converter(args=preset.get("cli"))
except Exception as e: except Exception as e:
msg = f"Invalid cli options. '{e!s}'." msg = f"Invalid cli options. '{e!s}'."
@ -270,26 +266,24 @@ class Presets(metaclass=Singleton):
return self return self
def get(self, id: str | None = None, name: str | None = None) -> Preset | None: def get(self, id_or_name: str) -> Preset | None:
""" """
Get the preset by id or name. Get the preset by id or name.
Args: Args:
id (str|None): The id of the preset. id_or_name (str): The id or name of the preset.
name (str|None): The name of the preset.
Returns: Returns:
Preset|None: The preset if found, None otherwise. Preset|None: The preset if found, None otherwise.
""" """
if not id and not name: if not id_or_name:
return None return None
for preset in self.get_all(): for preset in self.get_all():
if preset.id == id: if id_or_name not in (preset.id, preset.name):
return preset continue
if preset.name == name: return preset
return preset
return None return None

View file

@ -15,6 +15,7 @@ from .encoder import Encoder
from .Events import EventBus, Events, error, info, success from .Events import EventBus, Events, error, info, success
from .Scheduler import Scheduler from .Scheduler import Scheduler
from .Singleton import Singleton from .Singleton import Singleton
from .Utils import clean_item
LOG = logging.getLogger("tasks") LOG = logging.getLogger("tasks")
@ -140,13 +141,13 @@ class Tasks(metaclass=Singleton):
LOG.info(f"No tasks were defined in '{self._file}'.") LOG.info(f"No tasks were defined in '{self._file}'.")
return self return self
need_update = False need_save = False
for i, task in enumerate(tasks): for i, task in enumerate(tasks):
try: try:
task, task_status = self.clean_task(task) task, task_status = clean_item(task, keys=("cookies", "config"))
task = Task(**task) task = Task(**task)
if task_status: if task_status:
need_update = True need_save = True
except Exception as e: except Exception as e:
LOG.error(f"Failed to parse task at list position '{i}'. '{e!s}'.") LOG.error(f"Failed to parse task at list position '{i}'. '{e!s}'.")
continue continue
@ -160,7 +161,7 @@ class Tasks(metaclass=Singleton):
LOG.exception(e) LOG.exception(e)
LOG.error(f"Failed to queue task '{i}: {task.name}'. '{e!s}'.") LOG.error(f"Failed to queue task '{i}: {task.name}'. '{e!s}'.")
if need_update: if need_save:
LOG.info("Updating tasks file to remove old keys.") LOG.info("Updating tasks file to remove old keys.")
self.save(self.get_all()) self.save(self.get_all())
@ -223,6 +224,7 @@ class Tasks(metaclass=Singleton):
if task.get("cli"): if task.get("cli"):
try: try:
from .Utils import arg_converter from .Utils import arg_converter
arg_converter(args=task.get("cli")) arg_converter(args=task.get("cli"))
except Exception as e: except Exception as e:
msg = f"Invalid cli options. '{e!s}'." msg = f"Invalid cli options. '{e!s}'."
@ -266,27 +268,6 @@ class Tasks(metaclass=Singleton):
return self return self
def clean_task(self, task: dict) -> tuple[dict, bool]:
"""
Clean the task from old keys.
Args:
task (dict): The task to clean.
Returns:
tuple[dict, bool]: The cleaned task and a status if the task was cleaned.
"""
status = False
removedKeys = ["cookies", "config"]
for key in removedKeys:
if key in task:
status = True
task.pop(key)
return task, status
async def _runner(self, task: Task): async def _runner(self, task: Task):
""" """
Run the task. Run the task.

View file

@ -522,8 +522,6 @@ def arg_converter(
bad_options.update(item.items()) bad_options.update(item.items())
LOG.debug("Removed %i the following options: '%s'.", level, ", ".join(bad_options.values()))
for key in diff.copy(): for key in diff.copy():
if key not in bad_options: if key not in bad_options:
continue continue
@ -889,3 +887,41 @@ def get_files(base_path: str, dir: str | None = None):
) )
return contents return contents
def clean_item(item: dict, keys: list | tuple) -> tuple[dict, bool]:
"""
Remove given keys from a dictionary.
This function modifies the dictionary in place and returns a tuple
containing the modified dictionary and a boolean indicating
whether any keys were removed.
Args:
item (dict): The item to clean.
keys (list|tuple): The keys to remove.
Returns:
tuple[dict, bool]: The cleaned item and a the status of cleaning operation.
Raises:
TypeError: If item is not a dictionary or keys is not a list or tuple.
"""
status = False
if not isinstance(item, dict):
msg = "Item must be a dictionary."
raise TypeError(msg)
if not isinstance(keys, list | tuple):
msg = "Keys must be a list or tuple."
raise TypeError(msg)
for key in keys:
if key not in item:
continue
status = True
item.pop(key)
return item, status

View file

@ -78,7 +78,7 @@ class YTDLPOpts(metaclass=Singleton):
YTDLPOpts: The instance of the class YTDLPOpts: The instance of the class
""" """
preset = Presets.get_instance().get(name=name) preset = Presets.get_instance().get(name)
if not preset or "default" == name: if not preset or "default" == name:
return self return self
@ -118,25 +118,6 @@ class YTDLPOpts(metaclass=Singleton):
"temp": self._config.temp_path, "temp": self._config.temp_path,
} }
# @Deprecated - To be removed in future versions.
if not preset.cli:
if preset.postprocessors and isinstance(preset.postprocessors, list) and len(preset.postprocessors) > 0:
self._preset_opts["postprocessors"] = preset.postprocessors
if preset.args and isinstance(preset.args, dict) and len(preset.args) > 0:
bad_options = {k: v for d in REMOVE_KEYS for k, v in d.items()}
removed_options = []
for key, value in preset.args.items():
if key in bad_options:
removed_options.append(bad_options[key])
continue
self._preset_opts[key] = value
if len(removed_options) > 0:
LOG.warning(
"Removed the following options '%s' from '%s' args.", ", ".join(removed_options), preset.name
)
return self return self
def get_all(self, keep: bool = False) -> dict: def get_all(self, keep: bool = False) -> dict:

View file

@ -27,13 +27,13 @@
<div class="column is-12" v-if="showImport || !reference"> <div class="column is-12" v-if="showImport || !reference">
<label class="label is-inline" for="import_string"> <label class="label is-inline" for="import_string">
<span class="icon"><i class="fa-solid fa-file-import" /></span>
Import string Import string
</label> </label>
<div class="field has-addons"> <div class="field has-addons">
<div class="control has-icons-left is-expanded"> <div class="control is-expanded">
<input type="text" class="input" id="import_string" v-model="import_string" autocomplete="off"> <input type="text" class="input" id="import_string" v-model="import_string" autocomplete="off">
<span class="icon is-small is-left"><i class="fa-solid fa-t" /></span>
</div> </div>
<div class="control"> <div class="control">
@ -51,31 +51,35 @@
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
<div class="field"> <div class="field">
<label class="label is-inline" for="name" v-text="'Name'" /> <label class="label is-inline" for="name">
<div class="control has-icons-left"> <span class="icon"><i class="fa-solid fa-tag" /></span>
Name
</label>
<div class="control">
<input type="text" class="input" id="name" v-model="form.name" :disabled="addInProgress"> <input type="text" class="input" id="name" v-model="form.name" :disabled="addInProgress">
<span class="icon is-small is-left"><i class="fa-solid fa-n" /></span>
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>The name to refers to this custom settings.</span> <span>The name to refers to this preset of settings.</span>
</span> </span>
</div> </div>
</div> </div>
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
<div class="field"> <div class="field">
<label class="label is-inline" for="format" v-text="'Format'" /> <label class="label is-inline" for="format">
<div class="control has-icons-left"> <span class="icon"><i class="fa-solid fa-f" /></span>
Format
</label>
<div class="control">
<input type="text" class="input" id="format" v-model="form.format" :disabled="addInProgress"> <input type="text" class="input" id="format" v-model="form.format" :disabled="addInProgress">
<span class="icon is-small is-left"><i class="fa-solid fa-f" /></span>
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>The yt-dlp <code>[--format, -f]</code> video format code. see <NuxtLink <span>The yt-dlp <code>[--format, -f]</code> video format code. see <NuxtLink
href="https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#format-selection" target="blank">this href="https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#format-selection" target="blank">this
url</NuxtLink> for more info.</span>. Note, as this key is required, you can set the value to page</NuxtLink> for more info. Note, as this key is required, you can set the value to
<code>default</code> to let <code>yt-dlp</code> choose the best format. <code>default</code> to let <code>yt-dlp</code> choose the best format.</span>
</span> </span>
</div> </div>
</div> </div>
@ -83,12 +87,12 @@
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
<div class="field"> <div class="field">
<label class="label is-inline" for="folder"> <label class="label is-inline" for="folder">
<span class="icon"><i class="fa-solid fa-folder" /></span>
Default Download path Default Download path
</label> </label>
<div class="control has-icons-left"> <div class="control">
<input type="text" class="input" id="folder" placeholder="Leave empty to use default download path" <input type="text" class="input" id="folder" placeholder="Leave empty to use default download path"
v-model="form.folder" :disabled="addInProgress" list="folders"> v-model="form.folder" :disabled="addInProgress" list="folders">
<span class="icon is-small is-left"><i class="fa-solid fa-folder" /></span>
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
@ -101,19 +105,19 @@
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
<div class="field"> <div class="field">
<label class="label is-inline" for="output_template"> <label class="label is-inline" for="output_template">
<span class="icon"><i class="fa-solid fa-file" /></span>
Default Output template Default Output template
</label> </label>
<div class="control has-icons-left"> <div class="control">
<input type="text" class="input" id="output_template" :disabled="addInProgress" <input type="text" class="input" id="output_template" :disabled="addInProgress"
placeholder="Leave empty to use default template." v-model="form.template"> placeholder="Leave empty to use default template." v-model="form.template">
<span class="icon is-small is-left"><i class="fa-solid fa-file" /></span>
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Use this output template if non are given with URL. if not set, it will defaults to <span>Use this output template if non are given with URL. if not set, it will defaults to
<code>{{ config.app.output_template }}</code>. <code>{{ config.app.output_template }}</code>.
For more information <NuxtLink href="https://github.com/yt-dlp/yt-dlp#output-template" For more information visit <NuxtLink href="https://github.com/yt-dlp/yt-dlp#output-template"
target="_blank">visit this url</NuxtLink>. target="_blank">this page</NuxtLink>.
</span> </span>
</span> </span>
</div> </div>
@ -122,11 +126,12 @@
<div class="column is-12"> <div class="column is-12">
<div class="field"> <div class="field">
<label class="label is-inline" for="cli_options"> <label class="label is-inline" for="cli_options">
<span class="icon"><i class="fa-solid fa-terminal" /></span>
Command arguments for yt-dlp Command arguments for yt-dlp
</label> </label>
<div class="control"> <div class="control">
<input type="text" class="input" v-model="form.cli" id="cli_options" :disabled="addInProgress" <textarea class="textarea is-pre" v-model="form.cli" id="cli_options" :disabled="addInProgress"
placeholder="command options to use, e.g. --no-embed-metadata --no-embed-thumbnail"> placeholder="command options to use, e.g. --no-embed-metadata --no-embed-thumbnail" />
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
@ -141,8 +146,10 @@
<div class="column is-12"> <div class="column is-12">
<div class="field"> <div class="field">
<label class="label is-inline" for="cookies" <label class="label is-inline" for="cookies" v-tooltip="'Netscape HTTP Cookie format.'">
v-tooltip="'Netscape HTTP Cookie format.'">Cookies</label> <span class="icon"><i class="fa-solid fa-cookie" /></span>
Cookies
</label>
<div class="control"> <div class="control">
<textarea class="textarea is-pre" id="cookies" v-model="form.cookies" :disabled="addInProgress" <textarea class="textarea is-pre" id="cookies" v-model="form.cookies" :disabled="addInProgress"
placeholder="Leave empty to use default cookies" /> placeholder="Leave empty to use default cookies" />
@ -157,56 +164,7 @@
</span> </span>
</div> </div>
</div> </div>
<div class="column is-12" v-if="has_data(form?.args) || has_data(form?.postprocessors)">
<Message title="Deprecation Warning" class="is-background-warning-80 has-text-dark"
icon="fas fa-exclamation-circle">
<ul>
<li>
The <code>JSON yt-dlp config</code> and <code>JSON yt-dlp Post-Processors</code> fields are
deprecated and will be removed in the future. Please use the <b>Command arguments for yt-dlp</b>
field instead. The deprecated fields will still be working for now but they will stop, we suggest
that you migrate to the new field. as soon as possible to avoid any issues. No support will be
given for the deprecated fields.
</li>
<li>
If both fields are set, the <b>Command arguments for yt-dlp</b> field will take precedence over
the deprecated fields. and when you click save it will remove the deprecated fields.
</li>
</ul>
</Message>
</div>
<div class="column is-6-tablet is-12-mobile" v-if="has_data(form?.args)">
<div class="field">
<label class="label is-inline" for="args" v-tooltip="'Extends current global yt-dlp config. (JSON)'">
JSON yt-dlp config <span class="has-text-danger">(DEPRECATED)</span>
</label>
<div class="control">
<textarea class="textarea" id="args" v-model="form.args" :disabled="addInProgress"
placeholder="{}" />
</div>
<span class="help has-text-danger">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Deprecated, use <b>Command arguments for yt-dlp</b> field instead. </span>
</span>
</div>
</div>
<div class="column is-6-tablet is-12-mobile" v-if="has_data(form?.postprocessors)">
<div class="field">
<label class="label is-inline" for="postprocessors"
v-tooltip="'Things to do after download is done.'">
JSON yt-dlp Post-Processors <span class="has-text-danger">(DEPRECATED)</span>
</label>
<div class="control">
<textarea class="textarea" id="postprocessors" v-model="form.postprocessors"
:disabled="addInProgress" placeholder="[]" />
</div>
<span class="help has-text-danger">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Deprecated, use <b>Command arguments for yt-dlp</b> field instead. </span>
</span>
</div>
</div>
</div> </div>
</div> </div>
@ -319,55 +277,11 @@ const checkInfo = async () => {
return; return;
} }
if (form?.cli && '' !== form.cli) {
if ((has_data(copy?.args) || has_data(copy?.postprocessors))) {
if (false === confirm('cli options are set, this will remove the JSON yt-dlp config and Post-Processors. Are you sure?')) {
toast.warning('User cancelled the operation.')
return
}
}
if (copy?.args) {
delete copy.args
}
if (copy?.postprocessors) {
delete copy.postprocessors
}
}
else {
if (typeof copy.args === 'object') {
copy.args = JSON.stringify(copy.args, null, 2);
}
if (typeof copy.postprocessors === 'object') {
copy.postprocessors = JSON.stringify(copy.postprocessors, null, 2);
}
if (copy?.args) {
try {
copy.args = JSON.parse(copy.args)
} catch (e) {
toast.error(`Invalid JSON yt-dlp config. ${e.message}`)
return;
}
}
if (copy?.postprocessors) {
try {
copy.postprocessors = JSON.parse(copy.postprocessors)
} catch (e) {
toast.error(`Invalid JSON yt-dlp Post-Processors. ${e.message}`)
return;
}
}
}
// trim all fields in copy only if they are strings
for (const key in copy) { for (const key in copy) {
if (typeof copy[key] === 'string') { if (typeof copy[key] !== 'string') {
copy[key] = copy[key].trim() continue
} }
copy[key] = copy[key].trim()
} }
emitter('submit', { reference: toRaw(props.reference), preset: toRaw(copy) }); emitter('submit', { reference: toRaw(props.reference), preset: toRaw(copy) });
@ -417,18 +331,13 @@ const importItem = async () => {
try { try {
const item = JSON.parse(val) const item = JSON.parse(val)
console.log(item) if (item?._type || 'preset' !== item._type) {
toast.error(`Invalid import string. Expected type 'preset', got '${item._type ?? 'unknown'}'.`)
if ('preset' !== item._type) {
toast.error(`Invalid import string. Expected type 'preset', got '${item._type}'.`)
import_string.value = ''
return return
} }
if (form.format || form.cli) { if ((form.format || form.cli) && false === confirm('This will overwrite the current data. Are you sure?')) {
if (false === confirm('This will overwrite the current form fields. Are you sure?')) { return
return
}
} }
if (item.name) { if (item.name) {

View file

@ -71,15 +71,6 @@ div.is-centered {
</header> </header>
<div class="card-content"> <div class="card-content">
<div class="content"> <div class="content">
<template v-if="has_data(item?.args) || has_data(item.postprocessors)">
<p class="has-text-danger is-5 has-text-bold">
<span class="icon"><i class="fa-solid fa-triangle-exclamation" /></span>
<span>The preset is using deprecated options. It is recommended to update the preset to use the
new options. It will cease to work in future versions.</span>
</p>
<hr>
</template>
<p class="is-text-overflow" <p class="is-text-overflow"
v-if="item?.format && false === ['default', 'not_set'].includes(item.format)"> v-if="item?.format && false === ['default', 'not_set'].includes(item.format)">
<span class="icon"><i class="fa-solid fa-f" /></span> <span class="icon"><i class="fa-solid fa-f" /></span>
@ -307,11 +298,6 @@ const exportItem = item => {
} }
}) })
if (has_data(data?.args) || has_data(data?.postprocessors)) {
toast.error("v1.0 presets are no longer supported target for export. Please update your preset.")
return
}
let userData = {} let userData = {}
for (const key of Object.keys(data)) { for (const key of Object.keys(data)) {