Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 145 additions & 20 deletions src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ import {
kBloomButtonClass,
kBloomCanvasSelector,
} from "../toolbox/canvas/canvasElementUtils";
import { getString, post, useApiObject } from "../../utils/bloomApi";
import { wrapWithRequestPageContentDelay } from "./bloomEditing";
import { get, post, useApiObject } from "../../utils/bloomApi";
import { ILanguageNameValues } from "../bookSettings/FieldVisibilityGroup";
import OverflowChecker from "../OverflowChecker/OverflowChecker";

interface IMenuItemWithSubmenu extends ILocalizableMenuItemProps {
subMenu?: ILocalizableMenuItemProps[];
Expand Down Expand Up @@ -938,6 +940,76 @@ const CanvasElementContextControls: React.FunctionComponent<{
// So until I get a better idea, I'm just putting in a hard-coded list.
const fieldsControlledByAppearanceSystem = ["bookTitle"];

function adjustAutoSizeForVisibleEditableInTranslationGroup(tg: HTMLElement) {
const visibleEditable = tg.getElementsByClassName(
"bloom-editable bloom-visibility-code-on",
)[0] as HTMLElement;
if (!visibleEditable) {
return;
}
OverflowChecker.AdjustSizeOrMarkOverflow(visibleEditable);
}

function setEditableContentFromKnownDataBookValueIfAny(
editable: HTMLElement,
dataBook: string | null,
tg: HTMLElement,
) {
if (!dataBook) {
return;
}
wrapWithRequestPageContentDelay(
() =>
new Promise<void>((resolve, reject) => {
get(
`editView/getDataBookValue?lang=${editable.getAttribute("lang")}&dataBook=${dataBook}`,
(result) => {
try {
const content = result.data;
// content comes from a source that looked empty, we don't want to overwrite something the user may
// already have typed here.
// But it may well have something in it, because we usually have an empty paragraph to start with.
// To test whether it looks empty, we put the text into a newly created element and then
// see whether it's textContent is empty.
// The logic of overwriting something which the user has typed here is that if we keep what's here,
// then the user may never know that there was already something in that field. But if we overwrite, then
// the user can always correct it back to what he just typed.
const temp = document.createElement("div");
temp.innerHTML = content || "";
if (temp.textContent.trim() !== "")
editable.innerHTML = content;
adjustAutoSizeForVisibleEditableInTranslationGroup(
tg,
);
resolve();
} catch (error) {
reject(error);
}
},
(error) => {
reject(error);
},
);
}),
"setCanvasFieldValueFromDataBook",
);
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
}

function applyAppearanceClassForEditable(editable: HTMLElement) {
editable.classList.remove(
"bloom-contentFirst",
"bloom-contentSecond",
"bloom-contentThird",
);
if (editable.classList.contains("bloom-content1")) {
editable.classList.add("bloom-contentFirst");
} else if (editable.classList.contains("bloom-contentNational1")) {
editable.classList.add("bloom-contentSecond");
} else if (editable.classList.contains("bloom-contentNational2")) {
editable.classList.add("bloom-contentThird");
}
}

function makeLanguageMenuItem(
ce: HTMLElement,
menuOptions: IMenuItemWithSubmenu[],
Expand All @@ -962,7 +1034,7 @@ function makeLanguageMenuItem(
tg.setAttribute("data-default-languages", dataDefaultLang);
const editables = Array.from(
tg.getElementsByClassName("bloom-editable"),
);
) as HTMLElement[];
if (editables.length === 0) return; // not able to handle this yet.
let editableInLang = editables.find(
(e) => e.getAttribute("lang") === langCode,
Expand Down Expand Up @@ -1013,11 +1085,18 @@ function makeLanguageMenuItem(
}
}

setEditableContentFromKnownDataBookValueIfAny(
editableInLang,
dataBookValue,
tg,
);

// and conversely remove them from the others
for (const editable of editables) {
// Ensure visibility code is off for others.
editable.classList.remove("bloom-visibility-code-on");
}
adjustAutoSizeForVisibleEditableInTranslationGroup(tg);
setMenuOpen(false);
};

Expand Down Expand Up @@ -1183,6 +1262,37 @@ function makeFieldTypeMenuItem(
}
};

const removeConflictingStyleClasses = (
fieldType: {
editableClasses: string[];
classes: string[];
},
editables: HTMLElement[],
) => {
const newStyleClasses = new Set(
[...fieldType.classes, ...fieldType.editableClasses].filter((c) =>
c.endsWith("-style"),
),
);
if (newStyleClasses.size === 0) {
return;
}

const stripStyleClasses = (element: HTMLElement) => {
Array.from(element.classList).forEach((className) => {
if (
className.endsWith("-style") &&
!newStyleClasses.has(className)
) {
element.classList.remove(className);
}
});
};

stripStyleClasses(tg);
editables.forEach((editable) => stripStyleClasses(editable));
};
Comment thread
hatton marked this conversation as resolved.

const activeType = tg
.getElementsByClassName("bloom-editable bloom-visibility-code-on")[0]
?.getAttribute("data-book");
Expand All @@ -1194,9 +1304,21 @@ function makeFieldTypeMenuItem(
clearFieldTypeClasses();
for (const editable of Array.from(
tg.getElementsByClassName("bloom-editable"),
)) {
) as HTMLElement[]) {
editable.removeAttribute("data-book");
// There's a bit of guess-work involved in what would be most helpful here.
// clearFieldTypeClasses removes any field-type-specific style class,
// and we generally expect a bloom-editable to have some style class.
// Should it be Normal-style or Bubble-style? Bubble-style is the default
// for canvas elements, so I decided to go with that.
const hasStyleClass = Array.from(editable.classList).some(
(className) => className.endsWith("-style"),
);
if (!hasStyleClass) {
editable.classList.add("Bubble-style");
}
}
adjustAutoSizeForVisibleEditableInTranslationGroup(tg);
setMenuOpen(false);
},
icon: !activeType && <CheckIcon css={getMenuIconCss()} />,
Expand All @@ -1210,7 +1332,7 @@ function makeFieldTypeMenuItem(
clearFieldTypeClasses();
const editables = Array.from(
tg.getElementsByClassName("bloom-editable"),
);
) as HTMLElement[];
if (fieldType.readOnly) {
const readOnlyDiv = document.createElement("div");
readOnlyDiv.setAttribute(
Expand All @@ -1232,34 +1354,37 @@ function makeFieldTypeMenuItem(
// Reload the page to get the derived content loaded.
post("common/saveChangesAndRethinkPageEvent", () => {});
} else {
removeConflictingStyleClasses(fieldType, editables);
tg.classList.add(...fieldType.classes);
for (const editable of editables) {
editable.classList.add(...fieldType.editableClasses);
editable.setAttribute("data-book", fieldType.dataBook);
if (
fieldsControlledByAppearanceSystem.includes(
fieldType.dataBook,
)
) {
applyAppearanceClassForEditable(editable);
} else {
editable.classList.remove(
"bloom-contentFirst",
"bloom-contentSecond",
"bloom-contentThird",
);
}
if (
editable.classList.contains(
"bloom-visibility-code-on",
)
) {
getString(
`editView/getDataBookValue?lang=${editable.getAttribute("lang")}&dataBook=${fieldType.dataBook}`,
(content) => {
// content comes from a source that looked empty, we don't want to overwrite something the user may
// already have typed here.
// But it may well have something in it, because we usually have an empty paragraph to start with.
// To test whether it looks empty, we put the text into a newly created element and then
// see whether it's textContent is empty.
// The logic of overwriting something which the user has typed here is that if we keep what's here,
// then the user may never know that there was already something in that field. But if we overwrite, then
// the user can always correct it back to what he just typed.
const temp = document.createElement("div");
temp.innerHTML = content || "";
if (temp.textContent.trim() !== "")
editable.innerHTML = content;
},
setEditableContentFromKnownDataBookValueIfAny(
editable,
fieldType.dataBook,
tg,
);
}
}
adjustAutoSizeForVisibleEditableInTranslationGroup(tg);
}
setMenuOpen(false);
},
Expand Down