Skip to content

feat: Custom models editor with image picker and file browser integration#283

Draft
ditschi wants to merge 4 commits intotoniebox-reverse-engineering:developfrom
ditschi:feature/custom-model-editor
Draft

feat: Custom models editor with image picker and file browser integration#283
ditschi wants to merge 4 commits intotoniebox-reverse-engineering:developfrom
ditschi:feature/custom-model-editor

Conversation

@ditschi
Copy link

@ditschi ditschi commented Mar 9, 2026

This Adds

  • Full editor for tonies.custom.json (add, edit, delete entries)
  • Image management via reusable file browser (upload, select, custom_img)
  • Form validation, defaults, and localization

DEPENDS ON:: Backend changes from toniebox-reverse-engineering/teddycloud#426

Details

Custom Tonies Editor: Editor for managing custom tonie models

Custom tonies editor Custom models
grafik

Image Manager:

Shared UI for selecting and managing images in Teddy Studio and Custom Tonies Editor

Image Editor::Original Images
grafik

Image Editor::Upload and manage Custom Images -- also available in Tonie Studio
grafik

…tion

- Full editor for tonies.custom.json (add, edit, delete entries)
- Image management via reusable file browser (upload, select, custom_img)
- Form validation, defaults, and localization
@henryk86
Copy link
Collaborator

henryk86 commented Mar 10, 2026

Thanks for the pr and the added functionality and following the “architecture” 👍

if it works fine, please remove the wip things from the old approach and set your editor as default. No need for a setting to switch between a not finished wip and a complete feature.

Please stick to the icons in the action column instead of the text buttons, the gui should also work in mobile context.

In the custom model file browser… according to the screenshot: are you using no modal and display the editor below the list? In case of a lot of images this might be not ideal? Why not using a modal there?

Please also add an entry in the Changelog.md for your change.

For my better understanding: can you explain the default image thing a bit for me? Aaah. I think I got it. Custom - tonies.custom.json and default - tonies.json. But maybe there is a better wording than default. Like original or so?

have you test it with the image cache enabled?

how did you integrate the image manager in teddystudio? Is the search for a tonie as image source still working as before? Or did you change that completely? - found it, only the custom image part from pure temporary to this solution (would be a completely temporary solution still a nice feature? Not sure probably not.)

onImagePreviewClick?: (imageUrl: string) => void;
}

const IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".webp", ".gif"];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to constants.ts please

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created constants/fileTypes.ts,

Copy link
Author

@ditschi ditschi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will continue tomorrow with additionam manual tests (e.g. image cache)

onImagePreviewClick?: (imageUrl: string) => void;
}

const IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".webp", ".gif"];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created constants/fileTypes.ts,

@ditschi ditschi force-pushed the feature/custom-model-editor branch from bdf2ed3 to 8555fb1 Compare March 11, 2026 00:21
- Remove custom_editor_preview setting (editor always active)
- Ignore contrib/data/www/ (compiled web assets)
- Update teddycloud_web submodule (v0.7.0, PR feedback)
@ditschi ditschi force-pushed the feature/custom-model-editor branch from 8555fb1 to d382516 Compare March 11, 2026 00:25
@ditschi
Copy link
Author

ditschi commented Mar 11, 2026

Updated description with updated screenshots

@henryk86
Copy link
Collaborator

Thanks for resolving the comments.

Please use the default button colors (no green button for new model)

@ditschi
Copy link
Author

ditschi commented Mar 11, 2026

Thanks for resolving the comments.

Please use the default button colors (no green button for new model)

Updated,
also moved to better location. Also now save , discard and Changes only buttons will now be greyed out if they do not apply
grafik

top: 0,
zIndex: 5,
background: "var(--ant-color-bg-container, #141414)",
border: "1px solid #303030",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No hardcoded colors please. Use the ants token system. Also direct use of those -var(…)

Copy link
Collaborator

@henryk86 henryk86 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use e.g. token.colorBorder instead

@@ -1559,10 +1545,22 @@ export const ToniesCustomJsonEditor: React.FC<ToniesCustomJsonEditorProps> = ({
<div className="custom-tonie-table" style={{ border: "1px solid #303030", borderRadius: 8, padding: 8, marginBottom: 8 }}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No hardcoded colors please

@henryk86
Copy link
Collaborator

Thanks for resolving the comments.
Please use the default button colors (no green button for new model)

Updated, also moved to better location. Also now save , discard and Changes only buttons will now be greyed out if they do not apply grafik

I still doubt this is a good solution if you have hundreds of custom models. Why don’t you use a modal for that? Even if you want to edit multiple entries a modal would be better. Kind of the teddystudio label edit one where you can navigate through all labels and save on click next and so?

@henryk86
Copy link
Collaborator

And please stick to the general placing of buttons. As far as I can remember nowhere else the buttons are on top for saving. They are always on the bottom.

let columns: any[] = [
{
title: mode === "full" ? <div style={{ minHeight: 32 }}></div> : "",
title: mode === "full" ? t("fileBrowser.image", { defaultValue: "Bild" }) : "",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the default. (Or at least change to English)

display: source === "original" ? "block" : "none",
maxHeight: 520,
overflowY: "auto",
border: "1px solid #303030",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No hardcoded colors please

cursor: "pointer",
padding: 8,
border:
originalSelection === item ? "1px solid #1677ff" : "1px solid transparent",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No hardcoded colors please

Copy link
Collaborator

@henryk86 henryk86 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good idea to use this for adding files? This should be the light weight SELECT FILE file browser? So only for selecting existing files from teddycloud. Not for adding files.

With those changes it’s now kind of a duplicate to the filebrowser itself… I would prefer if you remove those changes and leave it as a select file filebrowser…

const modelKey = entry.model.trim().toLowerCase();
if (modelMap.has(modelKey)) {
return {
error: t("tonies.addNewCustomTonieModal.modelRequired") + ` (Duplikat: ${entry.model})`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stick to English

const pair = `${audioIds[j]}::${hashes[j].toLowerCase()}`;
if (pairMap.has(pair)) {
return {
error: `Doppelte audio_id+hash-Kombination erkannt: ${audioIds[j]}`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stick to English

const changedInputStyle = (changed: boolean) =>
changed
? ({
backgroundColor: "rgba(250, 173, 20, 0.18)",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No hardcoded colors please, use the antd token system


const getStatusBadge = (status: DraftStatus) => {
const label = getStatusLabel(status);
if (status === "clean") return <Tag style={{ background: "transparent", borderColor: "rgba(255,255,255,0.2)" }}>{label}</Tag>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to use the antd token colors, no hardcoded colors.

setPreviewOpen(true);
}}
style={{
border: "1px solid #5a5a5a",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No hardcoded colors please

}}
>
{isBroken ? (
<span style={{ display: "block", fontSize: 18, lineHeight: "44px", textAlign: "center" }}>🖼️</span>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only use antd icons please

message={t("tonies.customEditor.batch.title", { defaultValue: "Batch editing active" })}
description={t("tonies.customEditor.batch.description", {
defaultValue:
"Bei Mehrfachauswahl werden nur Serie, Release, Sprache, Kategorie und Bild auf alle ausgewählten Modelle angewendet.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default please (and if you won’t remove it, stick to English)

type="error"
showIcon
style={{ marginBottom: 8 }}
message={t("tonies.customEditor.validation.title", { defaultValue: "Please fix these issues first" })}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default please. Makes it easier to identify missing translation entries)

>
<Collapse.Panel
key="media"
header={t("tonies.customEditor.sections.media", { defaultValue: "Media and images" })}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default please

{t("tonies.addNewCustomTonieModal.pic")}
<Tooltip
title={t("tonies.customEditor.picHint", {
defaultValue:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default please

type="info"
showIcon
style={{ marginBottom: 12 }}
message={t("tonies.customEditor.coinHint.title", { defaultValue: "Hint: Audio assignment" })}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default please

message={t("tonies.customEditor.coinHint.title", { defaultValue: "Hint: Audio assignment" })}
description={t("tonies.customEditor.coinHint.description", {
defaultValue:
"Optional – only for metadata (title, tracks). Custom coins work without a model. Use only custom audio – official IDs overwrite real Tonies. Does not link NFC tags to audio.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default please

<>
<div
style={{
border: areTracksChanged ? "1px solid #faad14" : "1px solid transparent",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No hardcoded colors please

setPendingSavePlan(null);
}}
onOk={handlePreflightConfirm}
okText={t("tonies.customEditor.preflight.confirm", { defaultValue: "Save now" })}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default please

<Space direction="vertical">
<Typography.Text>
{t("tonies.customEditor.preflight.description", {
defaultValue: "Please confirm the scope of this save operation.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default please (Also in the next lines)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope you will refactor this file when you are finished? It’s a way too big. Move the functionality in one or more hooks please.

@henryk86
Copy link
Collaborator

In general please use always the antd token colors if you want to set a color. Hardcoded colors are problematic if you switch the theme.

I like to avoid default translations as it masks forgotten translations. As said earlier, you can use the gui —> community —> contribution —> translations to check if you have all translations in all 4 languages (not if you have missed one in all languages)

Console error logs don’t need to be translated but should always be in English.

I like the configurable columns in the models list. Are those chosen columns stored somewhere or do you have to select them always? (If the selection is stored somewhere, that might be a good thing for the filebrowser also)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants