Skip to content
Open
Show file tree
Hide file tree
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
32 changes: 21 additions & 11 deletions common/src/main/java/rearth/oracle/OracleClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@
import org.lwjgl.glfw.GLFW;
import rearth.oracle.ui.OracleScreen;
import rearth.oracle.ui.SearchScreen;
import rearth.oracle.util.BookMetadata;
import rearth.oracle.util.MarkdownParser;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public final class OracleClient {

public static final KeyBinding ORACLE_WIKI = new KeyBinding("key.oracle_index.open", GLFW.GLFW_KEY_H, "key.categories.oracle");
public static final KeyBinding ORACLE_SEARCH = new KeyBinding("key.oracle_index.search", -1, "key.categories.oracle");

public static final Set<String> LOADED_BOOKS = new HashSet<>();
public static final HashMap<String, BookMetadata> LOADED_BOOKS = new HashMap<>();
public static final HashMap<Identifier, BookItemLink> ITEM_LINKS = new HashMap<>();

public static ItemStack tooltipStack;
Expand Down Expand Up @@ -84,7 +86,7 @@ public static void init() {
*/
public static void openScreen(@Nullable String bookId, @Nullable Identifier entryId, @Nullable Screen parent) {
if (bookId != null)
OracleScreen.activeBook = bookId;
OracleScreen.activeBook = LOADED_BOOKS.get(bookId);
if (entryId != null)
OracleScreen.activeEntry = entryId;

Expand All @@ -96,19 +98,27 @@ private static void findAllResourceEntries() {
var resources = resourceManager.findResources("books", path -> path.getPath().endsWith(".mdx"));

LOADED_BOOKS.clear();
var supportedLanguages = new HashSet<String>();

for (var resourceId : resources.keySet()) {

var purePath = resourceId.getPath().replaceFirst("books/", "");
var segments = purePath.split("/");
var modId = segments[0]; // e.g. "oritech"
var entryPath = purePath.replaceFirst(modId + "/", ""); // e.g. "tools/wrench.mdx"
var entryFileName = segments[segments.length - 1]; // e.g. "wrench.mdx"
var entryDirectory = entryPath.replace(entryFileName, ""); // e.g. "tools" or "processing/reactor"

if (entryDirectory.startsWith(".translated")) continue; // skip / don't support translations for now


if (entryPath.startsWith(".translated")) {
var segments2 = entryDirectory.split("/");
if (segments2.length > 1) {
supportedLanguages.add(segments2[1]);
} // e.g. ".translated/zh_cn/tools/wrench.mdx" will return "zh_cn"
}

try {
var fileContent = new String(resources.get(resourceId).getInputStream().readAllBytes(), StandardCharsets.UTF_8);
try {
var fileContent = new String(resources.get(resourceId).getInputStream().readAllBytes(), StandardCharsets.UTF_8);
var fileComponents = MarkdownParser.parseFrontmatter(fileContent);
if (fileComponents.containsKey("related_items")) {
var baseString = fileComponents.get("related_items").replace("[", "").replace("]", "");
Expand All @@ -120,12 +130,12 @@ private static void findAllResourceEntries() {
}
}

} catch (IOException e) {
} catch (IOException e) {
Oracle.LOGGER.error("Unable to load book with id: " + resourceId);
throw new RuntimeException(e);
}
LOADED_BOOKS.add(modId);
throw new RuntimeException(e);
}

LOADED_BOOKS.put(modId, new BookMetadata(modId, supportedLanguages));
}
}

Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/rearth/oracle/command/OracleCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package rearth.oracle.command;

public class OracleCommand {
}
152 changes: 110 additions & 42 deletions common/src/main/java/rearth/oracle/ui/OracleScreen.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import io.wispforest.owo.ui.base.BaseOwoScreen;
import io.wispforest.owo.ui.base.BaseParentComponent;
import io.wispforest.owo.ui.component.Components;
import io.wispforest.owo.ui.component.ItemComponent;
import io.wispforest.owo.ui.component.LabelComponent;
Expand All @@ -25,7 +26,9 @@
import rearth.oracle.OracleClient;
import rearth.oracle.ui.components.ColoredCollapsibleContainer;
import rearth.oracle.ui.components.ScalableLabelComponent;
import rearth.oracle.util.BookMetadata;
import rearth.oracle.util.MarkdownParser;
import rearth.oracle.util.Util;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand All @@ -40,13 +43,14 @@ public class OracleScreen extends BaseOwoScreen<FlowLayout> {
private FlowLayout rootComponent;
private FlowLayout leftPanel;
private ScrollContainer<FlowLayout> outerContentContainer;
private BaseParentComponent langSelector;

private final Screen parent;

private boolean needsLayout = false;

public static Identifier activeEntry;
public static String activeBook;
public static BookMetadata activeBook;

private static final int wideContentWidth = 50; // in %

Expand Down Expand Up @@ -94,7 +98,7 @@ protected void build(FlowLayout rootComponent) {
outerContentContainer = Containers.verticalScroll(Sizing.fill(wideContentWidth), Sizing.fill(), contentContainer);
outerContentContainer.allowOverflow(true);
rootComponent.child(outerContentContainer);

buildModNavigation(leftPanel);

var outerNavigationBarContainer = Containers.verticalScroll(Sizing.content(3), Sizing.fill(80), navigationBar);
Expand Down Expand Up @@ -155,6 +159,9 @@ private void updateLayout() {
leftPanel.margins(Insets.of(0, 0, 10, 5));
outerContentContainer.horizontalSizing(Sizing.fixed(this.width - leftPanelSize - 20));
}

langSelector.positioning(Positioning.absolute(this.width - 120, 10))
.margins(Insets.top(10).withRight(5));
}

@Override
Expand All @@ -172,7 +179,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
}
}

private void loadContentContainer(Identifier filePath, String bookId) throws IOException {
private void loadContentContainer(Identifier filePath, BookMetadata bookMetadata) throws IOException {

contentContainer.clearChildren();
activeEntry = filePath;
Expand All @@ -181,31 +188,35 @@ private void loadContentContainer(Identifier filePath, String bookId) throws IOE
var resourceCandidate = resourceManager.getResource(filePath);

if (resourceCandidate.isEmpty()) {
System.out.println("No content file found for " + filePath);
Oracle.LOGGER.warn("No content file found for {}", filePath);
return;
}

var fileContent = new String(resourceCandidate.get().getInputStream().readAllBytes(), StandardCharsets.UTF_8);
var parsedTexts = MarkdownParser.parseMarkdownToOwoComponents(fileContent, bookId, link -> {
var parsedTexts = MarkdownParser.parseMarkdownToOwoComponents(fileContent, bookMetadata.getBookId(), link -> {

if (link.startsWith("http")) return false;

var pathSegments = filePath.getPath().split("/");
var newPath = "";
StringBuilder newPathBuilder = new StringBuilder();

// build path based on relative information
var parentIteration = link.startsWith("../") ? 1 : 0;
for (int i = 0; i < pathSegments.length - 1 - parentIteration; i++) {
newPath += pathSegments[i] + "/";
newPathBuilder.append(pathSegments[i]).append("/");
}

newPath = newPath.split("#")[0]; // anchors are not supported, so we just remove them
newPath += link.replace("../", "") + ".mdx"; // add file ending

newPathBuilder = new StringBuilder(newPathBuilder.toString().split("#")[0]); // anchors are not supported, so we just remove them
newPathBuilder.append(link.replace("../", "")).append(".mdx"); // add file ending

var newPath = newPathBuilder.toString();

Oracle.LOGGER.info("Loading content file: " + newPath);

var newId = Identifier.of(Oracle.MOD_ID, newPath);

try {
loadContentContainer(newId, bookId);
loadContentContainer(newId, bookMetadata);
} catch (IOException e) {
return false;
}
Expand Down Expand Up @@ -241,17 +252,17 @@ private void loadContentContainer(Identifier filePath, String bookId) throws IOE
}

private void buildModNavigation(FlowLayout buttonContainer) {
// collect all book ids
var bookIds = OracleClient.LOADED_BOOKS.stream()

// collect all book metadata
var bookMetadataList = OracleClient.LOADED_BOOKS.values().stream()
.sorted()
.toList();

var modSelectorDropdown = Components.dropdown(Sizing.content(3));
modSelectorDropdown.zIndex(5);

if (activeBook == null)
activeBook = bookIds.getFirst();
activeBook = bookMetadataList.getFirst();

if (activeEntry != null) {
try {
Expand All @@ -265,6 +276,8 @@ private void buildModNavigation(FlowLayout buttonContainer) {
if (this.height < 350) {
topMargins = 5;
}

buildLangSelector();

var bookTitleLabel = new ScalableLabelComponent(Text.translatable(Oracle.MOD_ID + ".title." + activeBook).formatted(Formatting.DARK_GRAY).append(" >").formatted(Formatting.DARK_GRAY), text -> false);
bookTitleLabel.scale = 1.5f;
Expand All @@ -275,12 +288,8 @@ private void buildModNavigation(FlowLayout buttonContainer) {
bookTitleWrapper.child(bookTitleLabel);
buttonContainer.child(bookTitleWrapper.zIndex(5));

bookTitleWrapper.mouseEnter().subscribe(() -> {
bookTitleWrapper.surface(MarkdownParser.ORACLE_PANEL_HOVER);
});
bookTitleWrapper.mouseLeave().subscribe(() -> {
bookTitleWrapper.surface(MarkdownParser.ORACLE_PANEL);
});
bookTitleWrapper.mouseEnter().subscribe(() -> bookTitleWrapper.surface(MarkdownParser.ORACLE_PANEL_HOVER));
bookTitleWrapper.mouseLeave().subscribe(() -> bookTitleWrapper.surface(MarkdownParser.ORACLE_PANEL));
bookTitleWrapper.mouseDown().subscribe((a, b, c) -> {
if (modSelectorDropdown.hasParent()) {
modSelectorDropdown.remove();
Expand All @@ -290,33 +299,95 @@ private void buildModNavigation(FlowLayout buttonContainer) {
return true;
});

for (var bookId : bookIds) {
for (var bookMetadata : bookMetadataList) {
var bookId = bookMetadata.getBookId();
modSelectorDropdown.button(Text.translatable(Oracle.MOD_ID + ".title." + bookId), elem -> {
activeBook = bookMetadata;
activeEntry = null;
modSelectorDropdown.remove();
buildModNavigationBar(bookId);
rootComponent.removeChild(langSelector);
buildLangSelector();
buildModNavigationBar(bookMetadata);
bookTitleLabel.text(Text.translatable(Oracle.MOD_ID + ".title." + bookId).formatted(Formatting.DARK_GRAY).append(" >").formatted(Formatting.DARK_GRAY));
activeBook = bookId;
});
}

buildModNavigationBar(activeBook);

}

private void buildLangSelector() {
var langLabel = Components.label(Text.translatable("oracle_index.label.lang"))
.color(Color.ofFormatting(Formatting.WHITE))
.margins(Insets.right(5));

var currentLangText = Components.label(
Util.getLanguageText(activeBook.getCurrentLanguage()).copy().formatted(Formatting.DARK_GRAY)
);

var currentLangTextWrapper = Containers.horizontalFlow(Sizing.content(), Sizing.content());
currentLangTextWrapper.surface(MarkdownParser.ORACLE_PANEL);
currentLangTextWrapper.mouseEnter().subscribe(() -> currentLangTextWrapper.surface(MarkdownParser.ORACLE_PANEL_HOVER));
currentLangTextWrapper.mouseLeave().subscribe(() -> currentLangTextWrapper.surface(MarkdownParser.ORACLE_PANEL));
currentLangTextWrapper.child(currentLangText.margins(Insets.of(3, 5, 3, 5)));

var langLabelWrapper = Containers.horizontalFlow(Sizing.content(), Sizing.content());
langLabelWrapper.child(langLabel);
langLabelWrapper.child(currentLangTextWrapper);

var langDropdown = Components.dropdown(Sizing.content());

// add supported languages
for (String lang : activeBook.getSupportedLanguages()) {
langDropdown.button(Util.getLanguageText(lang).copy().formatted(Formatting.WHITE), dropdownComponent -> {
activeBook.setCurrentLanguage(lang);
currentLangText.text(Util.getLanguageText(lang).copy().formatted(Formatting.DARK_GRAY));
langDropdown.remove();

// reload entries and content
if (activeEntry != null) {
try {
Oracle.LOGGER.info("Reloading content for " + activeEntry.getPath());
activeEntry = Identifier.of(Oracle.MOD_ID, activeBook.convertPathToCurrentLanguage(activeEntry.getPath()));
buildModNavigationBar(activeBook);
loadContentContainer(activeEntry, activeBook);
} catch (IOException e) {
Oracle.LOGGER.error("Failed to reload content: " + e.getMessage());
}
}
}).zIndex(10);
}

// show dropdown
langLabelWrapper.mouseDown().subscribe((mouseX, mouseY, button) -> {
if (langDropdown.hasParent()) {
langDropdown.remove();
return true;
}

langDropdown.positioning(Positioning.absolute(langLabelWrapper.x(), langLabelWrapper.y() + langLabelWrapper.height()));
rootComponent.child(langDropdown);
return true;
});

langSelector = langLabelWrapper
.positioning(Positioning.absolute(this.width - 120, 10))
.margins(Insets.top(10).withRight(5));
rootComponent.child(langSelector);
}

private void buildModNavigationBar(String bookId) {
private void buildModNavigationBar(BookMetadata bookMetadata) {
navigationBar.clearChildren();
buildNavigationEntriesForModPath(bookId, "", navigationBar);
buildNavigationEntriesForModPath(bookMetadata, "", navigationBar);
}

private void buildNavigationEntriesForModPath(String bookId, String path, FlowLayout container) {

private void buildNavigationEntriesForModPath(BookMetadata bookMetadata, String path, FlowLayout container) {
var resourceManager = MinecraftClient.getInstance().getResourceManager();
var metaPath = Identifier.of(Oracle.MOD_ID, "books/" + bookId + path + "/_meta.json");
var metaPath = Identifier.of(Oracle.MOD_ID, bookMetadata.getEntryPath(path) + "/_meta.json");
var resourceCandidate = resourceManager.getResource(metaPath);

if (resourceCandidate.isEmpty()) {
System.out.println("No _meta.json found for " + bookId + " at " + metaPath);
System.out.println("No _meta.json found for " + bookMetadata.getBookId() + " at " + metaPath);
return;
}

Expand All @@ -327,8 +398,9 @@ private void buildNavigationEntriesForModPath(String bookId, String path, FlowLa
if (activeEntry == null) {
var firstEntry = entries.stream().filter(elem -> !elem.directory).findFirst();
if (firstEntry.isPresent()) {
var firstEntryPath = Identifier.of(Oracle.MOD_ID, "books/" + bookId + path + "/" + firstEntry.get().id());
loadContentContainer(firstEntryPath, bookId);
// Oracle.LOGGER.info(bookMetadata.getEntryPath(path) + firstEntry.get().id());
var firstEntryPath = Identifier.of(Oracle.MOD_ID, bookMetadata.getEntryPath(path) + "/" + firstEntry.get().id());
loadContentContainer(firstEntryPath, bookMetadata);
activeEntry = firstEntryPath;
}
}
Expand All @@ -341,7 +413,7 @@ private void buildNavigationEntriesForModPath(String bookId, String path, FlowLa
Sizing.content(1),
Sizing.content(1),
Text.translatable(entry.name()).formatted(Formatting.WHITE), false);
buildNavigationEntriesForModPath(bookId, path + "/" + entry.id(), directoryContainer);
buildNavigationEntriesForModPath(bookMetadata, path + "/" + entry.id(), directoryContainer);
directoryContainer.margins(Insets.of(0, 0, 0, 0));
container.child(directoryContainer);

Expand All @@ -360,23 +432,19 @@ private void buildNavigationEntriesForModPath(String bookId, String path, FlowLa
levelContainers.add(directoryContainer);

} else {
final var labelPath = Identifier.of(Oracle.MOD_ID, "books/" + bookId + path + "/" + entry.id());
final var labelPath = Identifier.of(Oracle.MOD_ID, bookMetadata.getEntryPath(path) + "/" + entry.id());
final var labelText = Text.translatable(entry.name).formatted(Formatting.WHITE);
final var label = Components.label(labelText.formatted(Formatting.UNDERLINE));

label.mouseEnter().subscribe(() -> {
label.text(labelText.copy().formatted(Formatting.GRAY));
});
label.mouseLeave().subscribe(() -> {
label.text(labelText.copy());
});
label.mouseEnter().subscribe(() -> label.text(labelText.copy().formatted(Formatting.GRAY)));
label.mouseLeave().subscribe(() -> label.text(labelText.copy()));

label.mouseDown().subscribe((a, b, c) -> {
try {
loadContentContainer(labelPath, bookId);
loadContentContainer(labelPath, bookMetadata);
return true;
} catch (IOException e) {
Oracle.LOGGER.error(e.getMessage());
Oracle.LOGGER.error(e.getMessage(), e);
return false;
}
});
Expand Down
Loading