diff --git a/api/src/main/java/ru/dragonestia/msb3/api/boot/DefaultBootstrap.java b/api/src/main/java/ru/dragonestia/msb3/api/boot/DefaultBootstrap.java index 654e421..12c7c82 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/boot/DefaultBootstrap.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/boot/DefaultBootstrap.java @@ -6,12 +6,17 @@ import net.minestom.server.entity.GameMode; import net.minestom.server.event.player.PlayerChatEvent; import ru.dragonestia.msb3.api.module.FlatWorldModule; import ru.dragonestia.msb3.api.module.MotdModule; +import ru.dragonestia.msb3.api.module.PrometheusMetricsModule; import ru.dragonestia.msb3.api.module.ResourcePackRepositoryModule; import ru.dragonestia.msb3.api.resource.dialog.ButtonNumber; import ru.dragonestia.msb3.api.talk.dialogue.DialogueRenderer; import ru.dragonestia.msb3.api.talk.dialogue.DialogueTheme; +import ru.dragonestia.msb3.api.ui.PictureBanner; +import ru.dragonestia.msb3.resource.utils.ClassPreLoader; import team.unnamed.creative.ResourcePack; +import java.net.InetSocketAddress; + @Log4j2 public class DefaultBootstrap extends ServerInitializer { @@ -23,56 +28,58 @@ public class DefaultBootstrap extends ServerInitializer { public void onDefaultModulesLoaded() { MotdModule.init("logo.png", "msb3 server"); FlatWorldModule.init(GameMode.ADVENTURE); + PrometheusMetricsModule.init(new InetSocketAddress("0.0.0.0", 7500)); MinecraftServer.getGlobalEventHandler().addListener(PlayerChatEvent.class, event -> { var player = event.getPlayer(); - var render = new DialogueRenderer(player, DialogueTheme.builder().build()); - render.setText(""" - Абсолютно точно. - Я знаю точнo - невозможное возможно - Сойти с ума, влюбиться так неосторoжно - Найти тебя, не отпускать ни днём, ни ночью - Всё невозможное - возможно, знаю точно! - А где тебя искать, прошу ты мне ответь - В какие города мне за тобой лететь - Я готов на край Земли, я всё должен объяснить - Пойми, что без тебя я не умею жить - Я знаю точно - невозможное возможно - Сойти с ума, влюбиться так неосторожно - Найти тебя, не отпускать ни днём, ни ночью - Всё невозможное - возможно, знаю точно! - На-на-на-на (на-на-на-на), а-а, а-а - На-на-на-на (на-на-на-на), а-а, а-а - Всё готов делить, с тобой я пополам - Ты только мне поверь, я сделал выбор сам - Дай же мне последний шанс, я всё должен объяснить - Пойми, что без тебя я не умею жить - Я знаю точно - невозможное возможно - Сойти с ума, влюбиться так неосторожно - Найти тебя, не отпускать ни днём, ни ночью - Всё невозможное - возможно, знаю точно! - На-на-на-на (на-на-на-на), а-а, а-а - На-на-на-на (на-на-на-на), а-а, а-а - Я знаю точно - невозможное возможно - Сойти с ума, влюбиться так неосторожно - Найти тебя, не отпускать ни днём, ни ночью - Всё невозможное - возможно, знаю точно! - На-на-на-на (на-на-на-на), а-а, а-а - На-на-на-на (на-на-на-на), а-а, а-а"""); - - render.setButton(ButtonNumber.BUTTON_1, "Hello world!\nHello world!\nHello world!\nHello world!\nHello world!", ctx -> {}); - render.setButton(ButtonNumber.BUTTON_2, "I am a teapot", ctx -> {}); - render.setButton(ButtonNumber.BUTTON_3, "I love pizza\nMamma mia\nPeperoni\nPapa carlo\nZaebumba\nZaebumba", ctx -> {}); - render.setButton(ButtonNumber.BUTTON_4, "msb3 is top!", ctx -> {}); - - render.show(); + PictureBanner.TEST.show(player); +// var render = new DialogueRenderer(player, DialogueTheme.builder().build()); +// render.setText(""" +// Абсолютно точно. +// Я знаю точнo - невозможное возможно +// Сойти с ума, влюбиться так неосторoжно +// Найти тебя, не отпускать ни днём, ни ночью +// Всё невозможное - возможно, знаю точно! +// А где тебя искать, прошу ты мне ответь +// В какие города мне за тобой лететь +// Я готов на край Земли, я всё должен объяснить +// Пойми, что без тебя я не умею жить +// Я знаю точно - невозможное возможно +// Сойти с ума, влюбиться так неосторожно +// Найти тебя, не отпускать ни днём, ни ночью +// Всё невозможное - возможно, знаю точно! +// На-на-на-на (на-на-на-на), а-а, а-а +// На-на-на-на (на-на-на-на), а-а, а-а +// Всё готов делить, с тобой я пополам +// Ты только мне поверь, я сделал выбор сам +// Дай же мне последний шанс, я всё должен объяснить +// Пойми, что без тебя я не умею жить +// Я знаю точно - невозможное возможно +// Сойти с ума, влюбиться так неосторожно +// Найти тебя, не отпускать ни днём, ни ночью +// Всё невозможное - возможно, знаю точно! +// На-на-на-на (на-на-на-на), а-а, а-а +// На-на-на-на (на-на-на-на), а-а, а-а +// Я знаю точно - невозможное возможно +// Сойти с ума, влюбиться так неосторожно +// Найти тебя, не отпускать ни днём, ни ночью +// Всё невозможное - возможно, знаю точно! +// На-на-на-на (на-на-на-на), а-а, а-а +// На-на-на-на (на-на-на-на), а-а, а-а"""); +// +// render.setButton(ButtonNumber.BUTTON_1, "Всем привет!", ctx -> {}); +// render.setButton(ButtonNumber.BUTTON_2, "I am a teapot", ctx -> {}); +// render.setButton(ButtonNumber.BUTTON_3, "I love pizza\nMamma mia\nPeperoni\nPapa carlo\nZaebumba\nZaebumba", ctx -> {}); +// render.setButton(ButtonNumber.BUTTON_4, "msb3 is top!", ctx -> {}); +// +// render.show(); }); } @Override public void onInitializeResources(ResourcePack resourcePack) { - + ClassPreLoader.preload(PictureBanner.class); } @Override diff --git a/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerBootstrap.java b/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerBootstrap.java index 49122cc..dc1172c 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerBootstrap.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerBootstrap.java @@ -5,6 +5,7 @@ import lombok.experimental.UtilityClass; import lombok.extern.log4j.Log4j2; import net.kyori.adventure.key.Key; import net.minestom.server.MinecraftServer; +import ru.dragonestia.msb3.api.entity.PickableItem; import ru.dragonestia.msb3.api.item.ItemUtil; import ru.dragonestia.msb3.api.resource.DialogueResources; import ru.dragonestia.msb3.api.resource.MonologueResources; @@ -77,6 +78,7 @@ public final class ServerBootstrap { private void initDefaultModules() { ItemUtil.init(); + PickableItem.registerEvent(); } private void compileResourcePack() { diff --git a/api/src/main/java/ru/dragonestia/msb3/api/entity/PickableItem.java b/api/src/main/java/ru/dragonestia/msb3/api/entity/PickableItem.java new file mode 100644 index 0000000..18170fd --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/entity/PickableItem.java @@ -0,0 +1,36 @@ +package ru.dragonestia.msb3.api.entity; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.ItemEntity; +import net.minestom.server.entity.Player; +import net.minestom.server.event.item.PickupItemEvent; +import net.minestom.server.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.time.temporal.ChronoUnit; + +public class PickableItem extends ItemEntity { + + public PickableItem(@NotNull ItemStack itemStack) { + super(itemStack); + + setPickable(true); + setMergeable(false); + setPickupDelay(500, ChronoUnit.MILLIS); + } + + public static void registerEvent() { + MinecraftServer.getGlobalEventHandler().addListener(PickupItemEvent.class, event -> { + if (!(event.getItemEntity() instanceof PickableItem)) return; + if (!(event.getEntity() instanceof Player player)) return; + + var itemEntity = event.getItemEntity(); + var inv = player.getInventory(); + var item = itemEntity.getItemStack(); + + if (!inv.addItemStack(item)) { + event.setCancelled(true); + } + }); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/module/ResourcePackRepositoryModule.java b/api/src/main/java/ru/dragonestia/msb3/api/module/ResourcePackRepositoryModule.java index 38792a3..e89b3d2 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/module/ResourcePackRepositoryModule.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/module/ResourcePackRepositoryModule.java @@ -43,6 +43,8 @@ public class ResourcePackRepositoryModule { server.start(); MinecraftServer.getGlobalEventHandler().addListener(PlayerSpawnEvent.class, event -> { + if (!event.isFirstSpawn()) return; + var player = event.getPlayer(); player.sendResourcePacks(ResourcePackRequest.resourcePackRequest() diff --git a/api/src/main/java/ru/dragonestia/msb3/api/ui/PictureBanner.java b/api/src/main/java/ru/dragonestia/msb3/api/ui/PictureBanner.java new file mode 100644 index 0000000..46d6442 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/ui/PictureBanner.java @@ -0,0 +1,87 @@ +package ru.dragonestia.msb3.api.ui; + +import net.kyori.adventure.key.Key; +import net.minestom.server.entity.Player; +import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.InventoryType; +import ru.dragonestia.msb3.api.resource.dialog.GlyphPositions; +import ru.dragonestia.msb3.api.util.ResourceFromJar; +import ru.dragonestia.msb3.resource.Resources; +import ru.dragonestia.msb3.resource.glyph.GlyphComponentBuilder; +import ru.dragonestia.msb3.resource.glyph.GlyphImage; +import ru.dragonestia.msb3.resource.utils.ImageUtils; +import team.unnamed.creative.base.Writable; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class PictureBanner { + + public static final int CHEST_GUI_WIDTH = 176; + + public static PictureBanner TEST = new PictureBanner("test", ResourceFromJar.of("glyphs/test_banner.png")); + + private final GlyphImage glyph1; + private final GlyphImage glyph2; + private final GlyphImage glyph3; + private final GlyphImage glyph4; + + public PictureBanner(String identifier, Writable writable) { + BufferedImage image; + Writable part1; + Writable part2; + Writable part3; + Writable part4; + + try (var steam = new ByteArrayInputStream(writable.toByteArray())) { + image = ImageIO.read(steam); + var w = image.getWidth(); + var h = image.getHeight(); + + part1 = ImageUtils.imageToWritable(image.getSubimage(0, 0, w / 2, h / 2)); + part2 = ImageUtils.imageToWritable(image.getSubimage(w / 2, 0, w / 2, h / 2)); + part3 = ImageUtils.imageToWritable(image.getSubimage(0, h / 2, w / 2, h / 2)); + part4 = ImageUtils.imageToWritable(image.getSubimage(w / 2, h / 2, w / 2, h / 2)); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + glyph1 = Resources.createGlyph( + Key.key("msb3", "banner/" + identifier + "/1"), + part1, + GlyphPositions.guiBackground.height() / 2, + GlyphPositions.guiBackground.topPartsY() + ); + glyph2 = Resources.createGlyph( + Key.key("msb3", "banner/" + identifier + "/2"), + part2, + GlyphPositions.guiBackground.height() / 2, + GlyphPositions.guiBackground.topPartsY() + ); + glyph3 = Resources.createGlyph( + Key.key("msb3", "banner/" + identifier + "/3"), + part3, + GlyphPositions.guiBackground.height() / 2, + GlyphPositions.guiBackground.bottomPartsY() + ); + glyph4 = Resources.createGlyph( + Key.key("msb3", "banner/" + identifier + "/4"), + part4, + GlyphPositions.guiBackground.height() / 2, + GlyphPositions.guiBackground.bottomPartsY() + ); + } + + public void show(Player player) { + var builder = new GlyphComponentBuilder(); + builder.append(CHEST_GUI_WIDTH / 2 + 2 - glyph1.width(), glyph1); + builder.append(CHEST_GUI_WIDTH / 2 + 1, glyph2); + builder.append(CHEST_GUI_WIDTH / 2 + 2 - glyph3.width(), glyph3); + builder.append(CHEST_GUI_WIDTH / 2 + 1, glyph4); + + var inv = new Inventory(InventoryType.CHEST_6_ROW, builder.build()); + player.openInventory(inv); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/world/chunk/OutOfBoundsChunk.java b/api/src/main/java/ru/dragonestia/msb3/api/world/chunk/OutOfBoundsChunk.java index cce1d9d..11b8390 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/world/chunk/OutOfBoundsChunk.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/world/chunk/OutOfBoundsChunk.java @@ -2,11 +2,22 @@ package ru.dragonestia.msb3.api.world.chunk; import net.minestom.server.instance.DynamicChunk; import net.minestom.server.instance.Instance; +import net.minestom.server.instance.LightingChunk; import org.jetbrains.annotations.NotNull; -public class OutOfBoundsChunk extends DynamicChunk { +public interface OutOfBoundsChunk { - public OutOfBoundsChunk(@NotNull Instance instance, int chunkX, int chunkZ) { - super(instance, chunkX, chunkZ); + class Dynamic extends DynamicChunk implements OutOfBoundsChunk { + + public Dynamic(@NotNull Instance instance, int chunkX, int chunkZ) { + super(instance, chunkX, chunkZ); + } + } + + class Lighting extends LightingChunk implements OutOfBoundsChunk { + + public Lighting(@NotNull Instance instance, int chunkX, int chunkZ) { + super(instance, chunkX, chunkZ); + } } } diff --git a/api/src/main/java/ru/dragonestia/msb3/api/world/loader/PreloadedAnvilChunkLoader.java b/api/src/main/java/ru/dragonestia/msb3/api/world/loader/PreloadedAnvilChunkLoader.java index de3f689..51a2991 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/world/loader/PreloadedAnvilChunkLoader.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/world/loader/PreloadedAnvilChunkLoader.java @@ -30,7 +30,7 @@ public class PreloadedAnvilChunkLoader implements IChunkLoader { return CompletableFuture.supplyAsync(() -> { var sourceChunkData = source.provideChunk(chunkX, chunkZ); if (sourceChunkData.isEmpty()) { - return new OutOfBoundsChunk(instance, chunkX, chunkZ); + return new OutOfBoundsChunk.Dynamic(instance, chunkX, chunkZ); } return new SharedChunk(instance, chunkX, chunkZ, sourceChunkData.get()); }); diff --git a/editor/src/main/java/ru/dragonestia/editor/controller/DialogueController.java b/editor/src/main/java/ru/dragonestia/editor/controller/DialogueController.java new file mode 100644 index 0000000..ef9d188 --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/controller/DialogueController.java @@ -0,0 +1,16 @@ +package ru.dragonestia.editor.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.dragonestia.editor.controller.mapper.DialogueMapper; + +import java.util.List; + +@RestController +public class DialogueController { + + @GetMapping("/api/dialogues") + List allDialogues() { + return List.of(); // TODO + } +} diff --git a/editor/src/main/java/ru/dragonestia/editor/controller/mapper/DialogueMapper.java b/editor/src/main/java/ru/dragonestia/editor/controller/mapper/DialogueMapper.java new file mode 100644 index 0000000..8b7da3f --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/controller/mapper/DialogueMapper.java @@ -0,0 +1,51 @@ +package ru.dragonestia.editor.controller.mapper; + +import ru.dragonestia.editor.model.DialogueContext; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public record DialogueMapper( + String groupId, + String id, + String text, + List answers +) { + + public static DialogueMapper fromEntity(DialogueContext context) { + var buttons = new ArrayList(); + for (var button: context.getAnswers()) { + var actionData = new HashMap(); + for (var actionParam: button.getAction().getFields()) { + actionData.put(actionParam.getIdentifier(), actionParam.getValue()); + } + + var conditions = new ArrayList(); + for (var condition: button.getConditions()) { + var conditionData = new HashMap(); + for (var param: condition.getFields()) { + conditionData.put(param.getIdentifier(), param.getValue()); + } + conditions.add(new Condition(condition.getIdentifier(), conditionData)); + } + + buttons.add(new Answer(button.getIdentifier(), button.getText(), button.getAction().getIdentifier(), actionData, conditions)); + } + return new DialogueMapper(context.getGroupId(), context.getGroupId(), context.getText(), buttons); + } + + public record Answer( + String id, + String text, + String actionId, + Map actionData, + List conditions + ) {} + + public record Condition( + String id, + Map data + ) {} +} diff --git a/editor/src/main/java/ru/dragonestia/editor/model/DialogueAction.java b/editor/src/main/java/ru/dragonestia/editor/model/DialogueAction.java new file mode 100644 index 0000000..61a790a --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/model/DialogueAction.java @@ -0,0 +1,35 @@ +package ru.dragonestia.editor.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class DialogueAction { + + private String identifier; + private String name; + private String description; + private List fields = new ArrayList<>(); + private boolean builtIn; + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Field { + + private String identifier; + private String name; + private String description; + private FieldType type; + private String defaultValue; + } +} diff --git a/editor/src/main/java/ru/dragonestia/editor/model/DialogueCondition.java b/editor/src/main/java/ru/dragonestia/editor/model/DialogueCondition.java new file mode 100644 index 0000000..b11d92a --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/model/DialogueCondition.java @@ -0,0 +1,31 @@ +package ru.dragonestia.editor.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class DialogueCondition { + + private String identifier; + private String name; + private String description; + private ArrayList fields = new ArrayList<>(); + + @Getter + @Setter + public static class Field { + + private String identifier; + private String name; + private String description; + private FieldType type; + private String defaultValue; + } +} diff --git a/editor/src/main/java/ru/dragonestia/editor/model/DialogueContext.java b/editor/src/main/java/ru/dragonestia/editor/model/DialogueContext.java index 63ea105..766b86a 100644 --- a/editor/src/main/java/ru/dragonestia/editor/model/DialogueContext.java +++ b/editor/src/main/java/ru/dragonestia/editor/model/DialogueContext.java @@ -3,11 +3,54 @@ package ru.dragonestia.editor.model; import lombok.Getter; import lombok.Setter; +import java.util.ArrayList; + @Getter @Setter public class DialogueContext { + private String groupId; private String id; private String text; private String comment; + private ArrayList answers = new ArrayList<>(); + + @Getter + @Setter + public static class Answer { + + private String identifier; + private String text; + private String comment; + private Action action; + private ArrayList conditions = new ArrayList<>(); + } + + @Getter + @Setter + public static class Action { + + private String identifier; + private String name; + private ArrayList fields = new ArrayList<>(); + } + + @Getter + @Setter + public static class Condition { + + private String identifier; + private String name; + private ArrayList fields = new ArrayList<>(); + } + + @Getter + @Setter + public static class Field { + + private String identifier; + private String name; + private FieldType type; + private String value; + } } diff --git a/editor/src/main/java/ru/dragonestia/editor/model/FieldType.java b/editor/src/main/java/ru/dragonestia/editor/model/FieldType.java new file mode 100644 index 0000000..9f3e195 --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/model/FieldType.java @@ -0,0 +1,36 @@ +package ru.dragonestia.editor.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.function.Predicate; + +@Getter +@RequiredArgsConstructor +public enum FieldType { + STRING("Строка", input -> true), + TEXT("Текст", input -> true), + INTEGER("Целое число", input -> { + try { + Integer.parseInt(input); + return true; + } catch (Exception e) { + return false; + } + }), + BOOLEAN("Булево значение", input -> switch (input.toLowerCase()) { + case "true", "false" -> true; + default -> false; + }), + DOUBLE("Число с плавающей точкой", input -> { + try { + Double.parseDouble(input); + return true; + } catch (Exception e) { + return false; + } + }); + + private final String name; + private final Predicate validator; +} diff --git a/editor/src/main/java/ru/dragonestia/editor/page/DialogueGroupPage.java b/editor/src/main/java/ru/dragonestia/editor/page/DialogueGroupPage.java new file mode 100644 index 0000000..6ce9903 --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/page/DialogueGroupPage.java @@ -0,0 +1,97 @@ +package ru.dragonestia.editor.page; + +import com.vaadin.flow.component.Unit; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import ru.dragonestia.editor.page.browser.BrowserPage; +import ru.dragonestia.editor.page.browser.TabContainer; + +import java.util.ArrayList; +import java.util.List; + +public class DialogueGroupPage extends TabContainer { + + private final String groupIdentifier; + private final TextField fieldSearch; + private final Grid grid; + + public DialogueGroupPage(BrowserPage browser, String groupIdentifier) { + super(browser, "Группа: " + groupIdentifier); + this.groupIdentifier = groupIdentifier; + + add(new H2(groupIdentifier)); + add(new Paragraph( + "Здесь содержатся диалоги, которые связаны с данной группой. " + + "Нажмите на диалог в таблице чтобы редактировать или удалить его. " + + "Если хотите создать новый диалог, то нажмите кнопку для создания ниже. " + )); + + var buttonsLayout = new HorizontalLayout(); + buttonsLayout.setWidth("100%"); + add(buttonsLayout); + buttonsLayout.add(createButtonNewDialogue()); + buttonsLayout.add(createButtonUpdateDialogues()); + + add(fieldSearch = createFieldSearch()); + add(grid = createGrid()); + // TODO: init grid data + grid.setItems(List.of( + new DialogueEntry("test1", ""), + new DialogueEntry("test2", "а ывп выпыв пку пукфпкаырп ыварукып куруке авыфп авып"), + new DialogueEntry("fsdfdsfds", "111"), + new DialogueEntry("hello", "Приветственный диалог с игроком") + )); + } + + private Button createButtonUpdateDialogues() { + var button = new Button("Обновить список диалогов", VaadinIcon.REFRESH.create(), event -> { + // TODO + }); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + return button; + } + + private Button createButtonNewDialogue() { + var button = new Button("Создать новый диалог", VaadinIcon.PLUS.create(), e -> { + // TODO + }); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS); + return button; + } + + private TextField createFieldSearch() { + var field = new TextField(); + field.setPlaceholder("Поиск группы по идентификатору"); + field.setPrefixComponent(VaadinIcon.SEARCH.create()); + field.setValueChangeMode(ValueChangeMode.EAGER); + field.setWidth(100, Unit.PERCENTAGE); + field.addValueChangeListener(e -> { + var input = e.getValue().trim().toLowerCase(); + + // TODO + }); + return field; + } + + private Grid createGrid() { + var grid = new Grid(); + grid.addColumn(DialogueEntry::identifier).setHeader("Идентификатор").setWidth("15%").setFlexGrow(0); + grid.addColumn(DialogueEntry::comment).setHeader("Комментарий"); + grid.addItemClickListener(e -> { + var dialogueIdentifier = e.getItem().identifier(); + // TODO + + browser.openTab(new DialoguePage(browser, groupIdentifier, "test_id")); + }); + return grid; + } + + public record DialogueEntry(String identifier, String comment) {} +} diff --git a/editor/src/main/java/ru/dragonestia/editor/page/DialoguePage.java b/editor/src/main/java/ru/dragonestia/editor/page/DialoguePage.java new file mode 100644 index 0000000..e0f9083 --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/page/DialoguePage.java @@ -0,0 +1,325 @@ +package ru.dragonestia.editor.page; + +import com.vaadin.flow.component.Text; +import com.vaadin.flow.component.Unit; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.checkbox.Checkbox; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.dialog.Dialog; +import com.vaadin.flow.component.html.H3; +import com.vaadin.flow.component.html.ListItem; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.Scroller; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.component.textfield.NumberField; +import com.vaadin.flow.component.textfield.TextArea; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import ru.dragonestia.editor.model.DialogueAction; +import ru.dragonestia.editor.model.DialogueContext; +import ru.dragonestia.editor.model.FieldType; +import ru.dragonestia.editor.page.browser.BrowserPage; +import ru.dragonestia.editor.page.browser.TabContainer; + +import java.util.*; +import java.util.function.Supplier; + +public class DialoguePage extends TabContainer { + + private final static Random random = new Random(); + + private final String groupIdentifier; + private final String dialogueIdentifier; + private final TextArea fieldText; + private final TextArea fieldComment; + private final Button buttonNewAnswer; + private final VerticalLayout layoutAnswers; + private final List answers = new ArrayList<>(); + + private DialogueContext dialogueContext = new DialogueContext(); + + public DialoguePage(BrowserPage browser, String groupIdentifier, String dialogueIdentifier) { + super(browser, "Диалог: %s/%s".formatted(groupIdentifier, dialogueIdentifier)); + this.groupIdentifier = groupIdentifier; + this.dialogueIdentifier = dialogueIdentifier; + + var layout = new HorizontalLayout(); + layout.setPadding(false); + layout.setWidth(100, Unit.PERCENTAGE); + add(layout); + var leftLayout = new VerticalLayout(); + leftLayout.setWidth(45, Unit.PERCENTAGE); + leftLayout.setPadding(false); + layout.add(leftLayout); + var rightLayout = new VerticalLayout(); + rightLayout.setPadding(false); + layout.add(rightLayout); + + leftLayout.add(createFieldIdentifier()); + leftLayout.add(fieldText = createFieldText()); + leftLayout.add(createTextHelping()); + + var controlLayout = new HorizontalLayout(); + controlLayout.setWidth(100, Unit.PERCENTAGE); + controlLayout.setPadding(false); + leftLayout.add(controlLayout); + controlLayout.add(createButtonSave()); + controlLayout.add(createButtonDelete()); + + leftLayout.add(fieldComment = createFieldComment()); + + rightLayout.add(new H3("Ответы диалогов")); + rightLayout.add(buttonNewAnswer = createButtonNewAnswer()); + + layoutAnswers = new VerticalLayout(); + layoutAnswers.setPadding(false); + rightLayout.add(layoutAnswers); + updateAnswers(); + + //add(new DialogEditor(new DialogueContext())); + } + + private TextField createFieldIdentifier() { + var field = new TextField("Идентификатор группы/идентификатор диалога"); + field.setHelperText("Идентификатор диалога изменять нельзя"); + field.setValue("%s/%s".formatted(groupIdentifier, dialogueIdentifier)); + field.setReadOnly(true); + field.setWidth(100, Unit.PERCENTAGE); + return field; + } + + private TextArea createFieldText() { + var field = new TextArea("Текст диалога"); + field.setHeight(20, Unit.REM); + field.setWidth(100, Unit.PERCENTAGE); + return field; + } + + private VerticalLayout createTextHelping() { + var layout = new VerticalLayout(); + layout.setPadding(false); + layout.add(new Text("Здесь описаны подсказки с плейсхолдерами для диалога:")); // TODO + layout.add(new ListItem("Плейсхолдер 1")); + layout.add(new ListItem("Плейсхолдер 2")); + layout.add(new ListItem("Плейсхолдер 3")); + return layout; + } + + private TextArea createFieldComment() { + var field = new TextArea("Комментарий"); + field.setHelperText("Комментарием может являться любая пометка с кратким описанием диалога. Этот комментарий видно в списке диалогов на сайте."); + field.setMinHeight(10, Unit.REM); + field.setWidth(100, Unit.PERCENTAGE); + return field; + } + + private Button createButtonNewAnswer() { + var button = new Button("Добавить ответ диалога", VaadinIcon.PLUS.create()); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS); + button.addClickListener(e -> { + var answer = new Answer(); + answer.deletion = () -> deleteAnswer(answer); + answers.add(answer); + updateAnswers(); + }); + button.setWidth(100, Unit.PERCENTAGE); + return button; + } + + private Button createButtonSave() { + var button = new Button("Сохранить", VaadinIcon.DATABASE.create(), e -> { + // TODO + }); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS); + button.setWidth(49, Unit.PERCENTAGE); + return button; + } + + private Button createButtonDelete() { + var button = new Button("Удалить", VaadinIcon.TRASH.create(), e -> sendDeletionConfirm()); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR); + button.setWidth(49, Unit.PERCENTAGE); + return button; + } + + private void sendDeletionConfirm() { + var dialog = new Dialog("Удаление диалога"); + dialog.setWidth(30, Unit.REM); + dialog.getFooter().add(new Button("Отмена", e -> dialog.close())); + + var randomNumber = Integer.toString(random.nextInt(100, 999)); + + var layout = new VerticalLayout(); + dialog.add(layout); + layout.add(new Paragraph("Подтвердите что вы хотите удалить диалог. Введите код ниже в поле для ввода чтобы подтвердить удаление.")); + + var codeLayout = new H3(randomNumber); + layout.add(codeLayout); + + var buttonConfirm = new Button("Подтвердить удаление", e -> { + // TODO + dialog.close(); + }); + buttonConfirm.setEnabled(false); + buttonConfirm.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR); + buttonConfirm.setWidth(100, Unit.PERCENTAGE); + + var fieldConfirmation = new TextField(); + fieldConfirmation.setPlaceholder("Введите код, написанный выше"); + fieldConfirmation.setWidth(100, Unit.PERCENTAGE); + fieldConfirmation.setValueChangeMode(ValueChangeMode.EAGER); + fieldConfirmation.addValueChangeListener(e -> { + buttonConfirm.setEnabled(randomNumber.equals(e.getValue())); + }); + layout.add(fieldConfirmation); + + layout.add(buttonConfirm); + + dialog.open(); + } + + private void updateAnswers() { + buttonNewAnswer.setEnabled(answers.size() < 4); + + layoutAnswers.removeAll(); + for (var answer: answers) { + layoutAnswers.add(answer); + } + } + + private void deleteAnswer(Answer answer) { + answers.removeIf(target -> answer.uuid.equals(target.uuid)); + updateAnswers(); + } + + public static class Answer extends HorizontalLayout { + + private final UUID uuid = UUID.randomUUID(); + private Runnable deletion; + private final VerticalLayout layoutAction; + + public Answer() { + var layoutLeft = new VerticalLayout(); + add(layoutLeft); + var layoutRight = new VerticalLayout(); + add(layoutRight); + layoutAction = new VerticalLayout(); + layoutAction.setPadding(false); + + layoutLeft.add(createFieldText()); + layoutLeft.add(createSelectAction()); + layoutLeft.add(layoutAction); + + layoutRight.add(createButtonDeleteAnswer()); + layoutRight.add(createFieldIdentifier()); + layoutRight.add(createFieldComment()); + + setWidth(100, Unit.PERCENTAGE); + getStyle().set("background-color", "#EEEEEE"); + getStyle().set("border-radius", "26px"); + } + + private TextField createFieldText() { + var field = new TextField("Текст кнопки"); + field.setWidth(100, Unit.PERCENTAGE); + return field; + } + + private Button createButtonDeleteAnswer() { + var button = new Button("Удалить ответ", VaadinIcon.TRASH.create(), e -> { + if (deletion == null) return; + deletion.run(); + }); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR); + button.setWidth(100, Unit.PERCENTAGE); + return button; + } + + private TextField createFieldIdentifier() { + var field = new TextField("Идентификатор кнопки"); + field.setHelperText("Может быть пустым. Этот параметр нужен чтобы запоминать какие кнопки нажимал игрок чтобы потом делать различные проверки на это."); + field.setWidth(100, Unit.PERCENTAGE); + return field; + } + + private TextArea createFieldComment() { + var field = new TextArea("Комментарий"); + field.setHelperText("Комментарием может являться любая пометка с кратким описанием ответа диалога."); + field.setWidth(100, Unit.PERCENTAGE); + field.setHeight(10, Unit.REM); + return field; + } + + private ComboBox createSelectAction() { + var comboBox = new ComboBox("Действие ответа диалога"); + comboBox.setHelperText("Выполняется, когда игрок нажимает на кнопку выбора ответа внутри диалога"); + comboBox.setWidth(100, Unit.PERCENTAGE); + comboBox.setItemLabelGenerator(action -> "%s [%s]".formatted(action.getName(), action.getIdentifier())); + + var items = List.of( + new DialogueAction("close", "Закрыть диалог", "Просто закрывает диалог", new ArrayList<>(), true), + new DialogueAction("dialogue", "Перейти к диалогу", "Перейти к другому диалогу", List.of( + new DialogueAction.Field("groupId", "Id-группы диалога", "", FieldType.STRING, ""), + new DialogueAction.Field("dialogueId", "Id диалога", "", FieldType.STRING, "") + ), true), + new DialogueAction("test_params", "Тест всех полей", "", List.of( + new DialogueAction.Field("param1", "Строка", "", FieldType.STRING, ""), + new DialogueAction.Field("param2", "Текст", "", FieldType.TEXT, ""), + new DialogueAction.Field("param3", "Булево значение", "", FieldType.BOOLEAN, ""), + new DialogueAction.Field("param4", "Целое число", "", FieldType.INTEGER, ""), + new DialogueAction.Field("param5", "Число с плавающей точкой", "", FieldType.DOUBLE, "") + ), true) + ); + comboBox.setItems(items); + comboBox.setValue(items.getFirst()); + comboBox.addValueChangeListener(e -> { + updateAction(e.getValue(), new HashMap<>()); + }); + return comboBox; + } + + private void updateAction(DialogueAction action, Map params) { + layoutAction.removeAll(); + var fieldMapper = new HashMap>(); + for (var param: action.getFields()) { + var component = switch (param.getType()) { + case STRING -> { + var field = new TextField(param.getName()); + field.setValue(param.getDefaultValue()); + fieldMapper.put(param.getIdentifier(), field::getValue); + yield field; + } + case TEXT -> { + var field = new TextArea(param.getName()); + field.setValue(param.getDefaultValue()); + fieldMapper.put(param.getIdentifier(), field::getValue); + yield field; + } + case BOOLEAN -> { + var field = new Checkbox(param.getName()); + field.setValue(Boolean.parseBoolean(param.getDefaultValue())); + fieldMapper.put(param.getIdentifier(), () -> Boolean.toString(field.getValue())); + yield field; + } + case INTEGER -> { + var field = new IntegerField(param.getName()); + fieldMapper.put(param.getIdentifier(), () -> Integer.toString(field.getValue())); + yield field; + } + case DOUBLE -> { + var field = new NumberField(param.getName()); + fieldMapper.put(param.getIdentifier(), () -> Double.toString(field.getValue())); + yield field; + } + }; + + component.setWidth(100, Unit.PERCENTAGE); + layoutAction.add(component); + } + } + } +} diff --git a/editor/src/main/java/ru/dragonestia/editor/page/HomePage.java b/editor/src/main/java/ru/dragonestia/editor/page/HomePage.java new file mode 100644 index 0000000..07f6f50 --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/page/HomePage.java @@ -0,0 +1,166 @@ +package ru.dragonestia.editor.page; + +import com.vaadin.flow.component.Html; +import com.vaadin.flow.component.Unit; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.dialog.Dialog; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.*; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import ru.dragonestia.editor.page.browser.BrowserPage; +import ru.dragonestia.editor.page.browser.TabContainer; + +import java.util.ArrayList; +import java.util.function.BiConsumer; + +public class HomePage extends TabContainer { + + public HomePage(BrowserPage browser) { + super(browser, "Домашняя страница"); + + add(new H3("Диалоги")); + addLink("Управление группами диалогов", this::findOrCreateDialog); + addLink("Список всех групп диалогов", this::listAllDialogueGroups); + add(new H3("Действия ответов диалога")); + addLink("Создание/редактирование действий ответов диалога", this::findOrCreateDialogAction); + addLink("Список всех действий ответов диалога", this::listAllDialogueActions); + add(new H3("Условия ответов диалога")); + addLink("Создание/редактирование условий ответов диалога", this::findOrCreateDialogCondition); + addLink("Список всех ответов диалога", this::listAllDialogueConditions); + } + + private void addLink(String label, BiConsumer content) { + var component = new Span(new Html("" + label + "")); + component.addClickListener(event -> { + var dialog = new Dialog(); + dialog.setMinHeight(30, Unit.PERCENTAGE); + dialog.setWidth(40, Unit.PERCENTAGE); + + dialog.getHeader().add(new H2(label)); + + var layout = new VerticalLayout(); + layout.setPadding(false); + content.accept(layout, dialog::close); + dialog.add(layout); + + var closeButton = new Button("Close"); + closeButton.addThemeVariants(); + closeButton.addClickListener(e -> dialog.close()); + dialog.getFooter().add(closeButton); + + dialog.open(); + }); + add(new ListItem(component)); + } + + private void findOrCreateDialog(VerticalLayout layout, Runnable closeWindow) { + layout.add(new Paragraph( + "Введите идентификатор группы диалога чтобы перейти к созданию или редактированию группы диалога. " + + "Группы диалогов нужны чтобы упростить группировку диалогов. " + )); + + var fieldIdentifier = new TextField("Идентификатор группы"); + fieldIdentifier.setHelperText("Идентификатор может содержать только символы английского алфавита, цифры и символ нижнего подчеркивания"); + fieldIdentifier.setWidth(100, Unit.PERCENTAGE); + layout.add(fieldIdentifier); + + var buttons = new HorizontalLayout(); + buttons.setWidth(100, Unit.PERCENTAGE); + buttons.setJustifyContentMode(JustifyContentMode.CENTER); + layout.add(buttons); + + var buttonCheckGroup = new Button("Проверить существование группы", e -> { + // TODO + }); + buttonCheckGroup.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + buttonCheckGroup.setWidth(49, Unit.PERCENTAGE); + buttons.add(buttonCheckGroup); + + var createOrFind = new Button("Создать/редактировать", e -> { + var groupIdentifier = fieldIdentifier.getValue().trim().toLowerCase(); + + if (groupIdentifier.isEmpty()) return; + if (!groupIdentifier.matches("^[aA-zZ\\d_]+$")) { + Notification.show("Идентификатор группы содержит недопустимые символы.") + .addThemeVariants(NotificationVariant.LUMO_ERROR); + return; + } + + // TODO + + browser.openTab(new DialogueGroupPage(browser, groupIdentifier)); + closeWindow.run(); + }); + createOrFind.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS); + createOrFind.setWidth(49, Unit.PERCENTAGE); + buttons.add(createOrFind); + } + + private void listAllDialogueGroups(VerticalLayout layout, Runnable closeWindow) { + var buttonRefresh = new Button("Обновить список", VaadinIcon.REFRESH.create()); + layout.add(buttonRefresh); + + var fieldSearch = new TextField(); + fieldSearch.setPlaceholder("Поиск группы по идентификатору"); + fieldSearch.setPrefixComponent(VaadinIcon.SEARCH.create()); + fieldSearch.setValueChangeMode(ValueChangeMode.EAGER); + fieldSearch.setWidth(100, Unit.PERCENTAGE); + layout.add(fieldSearch); + + var grid = new Grid(); + var data = new ArrayList(); + // TODO: initial data loading + grid.setItems(data); + grid.addColumn(DialogGroupEntry::id).setHeader("Id группы").setWidth("25%").setFlexGrow(0); + grid.addColumn(DialogGroupEntry::dialoguesInside).setHeader("Кол-во диалогов").setWidth("20%").setFlexGrow(0); + grid.addComponentColumn(obj -> new Paragraph(obj.comment())).setHeader("Комментарий"); + grid.addItemClickListener(e -> { + var item = e.getItem(); + // TODO: open dialogue group + + closeWindow.run(); + }); + layout.add(grid); + + fieldSearch.addValueChangeListener(e -> { + var input = e.getValue().trim().toLowerCase(); + var newList = new ArrayList<>(data); + newList.removeIf(entry -> !entry.id().toLowerCase().startsWith(input)); + grid.setItems(newList); + }); + + buttonRefresh.addClickListener(e -> { + // TODO + }); + + layout.add(new Paragraph( + "В этом списке отображаются группы, которые имеют хоть какое-то количество диалогов внутри. " + + "Нажмите на строчку с диалогом чтобы заглянуть внутрь. " + )); + } + + private void findOrCreateDialogAction(VerticalLayout layout, Runnable closeWindow) { + + } + + private void listAllDialogueActions(VerticalLayout layout, Runnable closeWindow) { + + } + + private void findOrCreateDialogCondition(VerticalLayout layout, Runnable closeWindow) { + + } + + private void listAllDialogueConditions(VerticalLayout layout, Runnable closeWindow) { + + } + + private record DialogGroupEntry(String id, int dialoguesInside, String comment) {} +} diff --git a/editor/src/main/java/ru/dragonestia/editor/page/Page.java b/editor/src/main/java/ru/dragonestia/editor/page/Page.java deleted file mode 100644 index 77d612c..0000000 --- a/editor/src/main/java/ru/dragonestia/editor/page/Page.java +++ /dev/null @@ -1,81 +0,0 @@ -package ru.dragonestia.editor.page; - -import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.router.BeforeEnterEvent; -import com.vaadin.flow.router.BeforeEnterObserver; -import com.vaadin.flow.router.BeforeLeaveEvent; -import com.vaadin.flow.router.BeforeLeaveObserver; - -import java.util.Optional; - -public class Page extends VerticalLayout implements BeforeEnterObserver, BeforeLeaveObserver { - - protected void init(QueryParams params) {} - - protected void destroy() {} - - @Override - public final void beforeEnter(BeforeEnterEvent event) { - init(key -> event.getRouteParameters().get(key)); - } - - @Override - public final void beforeLeave(BeforeLeaveEvent event) { - destroy(); - } - - public interface QueryParams { - - Optional String(String key); - - default Optional Integer(String key) { - return String(key).map(str -> { - try { - return Integer.parseInt(str); - } catch (Exception ex) { - return null; - } - }); - } - - default Optional Long(String key) { - return String(key).map(str -> { - try { - return Long.parseLong(str); - } catch (Exception ex) { - return null; - } - }); - } - - default Optional Float(String key) { - return String(key).map(str -> { - try { - return Float.parseFloat(str); - } catch (Exception ex) { - return null; - } - }); - } - - default Optional Double(String key) { - return String(key).map(str -> { - try { - return Double.parseDouble(str); - } catch (Exception ex) { - return null; - } - }); - } - - default Optional Boolean(String key) { - return String(key).map(str -> { - try { - return Boolean.parseBoolean(str); - } catch (Exception ex) { - return null; - } - }); - } - } -} diff --git a/editor/src/main/java/ru/dragonestia/editor/page/TestPage.java b/editor/src/main/java/ru/dragonestia/editor/page/TestPage.java deleted file mode 100644 index de91809..0000000 --- a/editor/src/main/java/ru/dragonestia/editor/page/TestPage.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.dragonestia.editor.page; - -import com.vaadin.flow.router.Route; -import jakarta.annotation.PostConstruct; -import ru.dragonestia.editor.component.DialogEditor; -import ru.dragonestia.editor.model.DialogueContext; - -@Route("/") -public class TestPage extends Page { - - @PostConstruct - void init() { - var ctx = new DialogueContext(); - add(new DialogEditor(ctx)); - } -} diff --git a/editor/src/main/java/ru/dragonestia/editor/page/browser/BrowserPage.java b/editor/src/main/java/ru/dragonestia/editor/page/browser/BrowserPage.java new file mode 100644 index 0000000..8c458e4 --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/page/browser/BrowserPage.java @@ -0,0 +1,57 @@ +package ru.dragonestia.editor.page.browser; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.Unit; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.tabs.Tab; +import com.vaadin.flow.component.tabs.TabSheet; +import com.vaadin.flow.router.Route; +import ru.dragonestia.editor.page.HomePage; + +@Route("/") +public class BrowserPage extends VerticalLayout { + + private final TabSheet tabs; + + public BrowserPage() { + tabs = new TabSheet(); + tabs.setWidth(100, Unit.PERCENTAGE); + //tabs.setSuffixComponent(createNewTabButton()); + add(tabs); + + openHomePage(); + } + + private Component createNewTabButton() { + return new Button(VaadinIcon.PLUS.create(), event -> openHomePage()); + } + + private void openHomePage() { + var tab = new Tab(new Span(VaadinIcon.HOME.create())); + var style = tab.getStyle(); + style.setPaddingTop("0rem"); + style.setPaddingBottom("0rem"); + + tabs.add(tab, new HomePage(this)); + } + + public void openTab(TabContainer container) { + var tab = new Tab(); + var style = tab.getStyle(); + style.setPaddingTop("0rem"); + style.setPaddingBottom("0rem"); + tab.setLabel(container.getTitle()); + container.setTab(tab); + + var closeTabButton = new Span(VaadinIcon.CLOSE_SMALL.create()); + closeTabButton.getStyle().setMarginLeft("1rem"); + closeTabButton.addClickListener(event -> tabs.remove(tab)); + tab.add(closeTabButton); + + tabs.add(tab, container); + tabs.setSelectedTab(tab); + } +} diff --git a/editor/src/main/java/ru/dragonestia/editor/page/browser/TabContainer.java b/editor/src/main/java/ru/dragonestia/editor/page/browser/TabContainer.java new file mode 100644 index 0000000..87bc3ad --- /dev/null +++ b/editor/src/main/java/ru/dragonestia/editor/page/browser/TabContainer.java @@ -0,0 +1,25 @@ +package ru.dragonestia.editor.page.browser; + +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.tabs.Tab; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public abstract class TabContainer extends VerticalLayout { + + protected final BrowserPage browser; + private String title; + private Tab tab; + + public TabContainer(BrowserPage browser, String title) { + this.browser = browser; + this.title = title; + } + + public void setTitle(String title) { + this.title = title; + tab.setLabel(title); + } +} diff --git a/editor/src/main/resources/application.yaml b/editor/src/main/resources/application.yaml index 49c10cc..634aaf0 100644 --- a/editor/src/main/resources/application.yaml +++ b/editor/src/main/resources/application.yaml @@ -14,5 +14,8 @@ spring: hibernate: ddl-auto: validate + mvc: + static-path-pattern: '/static/**' + server: port: 8080