diff options
| author | shedaniel <daniel@shedaniel.me> | 2021-11-21 19:10:50 +0800 |
|---|---|---|
| committer | shedaniel <daniel@shedaniel.me> | 2021-11-21 19:10:50 +0800 |
| commit | 85e72e9a9bc70cf283ba872172985b338be59233 (patch) | |
| tree | bce73cb38c00b73d1323c7a014701ee638f2224b /api/src/main/java/me/shedaniel | |
| parent | a437fe0560478c636526862679ba1098688bf2c1 (diff) | |
| parent | c4cdd997f981d0a3b763140d1515a73941b18f5c (diff) | |
| download | RoughlyEnoughItems-85e72e9a9bc70cf283ba872172985b338be59233.tar.gz RoughlyEnoughItems-85e72e9a9bc70cf283ba872172985b338be59233.tar.bz2 RoughlyEnoughItems-85e72e9a9bc70cf283ba872172985b338be59233.zip | |
Merge remote-tracking branch 'origin/6.x-1.17' into 7.x-1.18
# Conflicts:
# api/src/main/java/me/shedaniel/rei/api/common/transfer/info/MenuInfo.java
# gradle.properties
# runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java
# runtime/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java
Diffstat (limited to 'api/src/main/java/me/shedaniel')
10 files changed, 315 insertions, 19 deletions
diff --git a/api/src/main/java/me/shedaniel/rei/api/client/config/ConfigObject.java b/api/src/main/java/me/shedaniel/rei/api/client/config/ConfigObject.java index 13ba8edf5..4ab9afc7b 100644 --- a/api/src/main/java/me/shedaniel/rei/api/client/config/ConfigObject.java +++ b/api/src/main/java/me/shedaniel/rei/api/client/config/ConfigObject.java @@ -148,6 +148,9 @@ public interface ConfigObject { List<EntryStackProvider<?>> getFilteredStackProviders(); @ApiStatus.Experimental + boolean shouldFilterDisplays(); + + @ApiStatus.Experimental boolean shouldAsyncSearch(); @ApiStatus.Experimental diff --git a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Tooltip.java b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Tooltip.java index 370caa671..7f691dcfe 100644 --- a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Tooltip.java +++ b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Tooltip.java @@ -23,6 +23,7 @@ package me.shedaniel.rei.api.client.gui.widgets; +import dev.architectury.utils.Env; import dev.architectury.utils.EnvExecutor; import me.shedaniel.math.Point; import me.shedaniel.rei.api.client.REIRuntime; @@ -130,7 +131,7 @@ public interface Tooltip { Tooltip withContextStack(EntryStack<?> stack); default void queue() { - EnvExecutor.runInEnv(EnvType.CLIENT, () -> () -> REIRuntime.getInstance().queueTooltip(this)); + EnvExecutor.runInEnv(Env.CLIENT, () -> () -> REIRuntime.getInstance().queueTooltip(this)); } @ApiStatus.NonExtendable diff --git a/api/src/main/java/me/shedaniel/rei/api/client/registry/display/TransferDisplayCategory.java b/api/src/main/java/me/shedaniel/rei/api/client/registry/display/TransferDisplayCategory.java index 5cc0fc459..00843f57a 100644 --- a/api/src/main/java/me/shedaniel/rei/api/client/registry/display/TransferDisplayCategory.java +++ b/api/src/main/java/me/shedaniel/rei/api/client/registry/display/TransferDisplayCategory.java @@ -35,6 +35,8 @@ import org.jetbrains.annotations.ApiStatus; import java.util.List; @Environment(EnvType.CLIENT) +@Deprecated +@ApiStatus.ScheduledForRemoval public interface TransferDisplayCategory<T extends Display> extends DisplayCategory<T> { @ApiStatus.OverrideOnly @ApiStatus.ScheduledForRemoval diff --git a/api/src/main/java/me/shedaniel/rei/api/client/registry/transfer/TransferHandler.java b/api/src/main/java/me/shedaniel/rei/api/client/registry/transfer/TransferHandler.java index 9ec3a3a15..2d8a8ef83 100644 --- a/api/src/main/java/me/shedaniel/rei/api/client/registry/transfer/TransferHandler.java +++ b/api/src/main/java/me/shedaniel/rei/api/client/registry/transfer/TransferHandler.java @@ -23,7 +23,6 @@ package me.shedaniel.rei.api.client.registry.transfer; -import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import me.shedaniel.rei.api.client.registry.display.TransferDisplayCategory; import me.shedaniel.rei.api.common.display.Display; @@ -36,6 +35,7 @@ import net.minecraft.world.inventory.AbstractContainerMenu; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import java.util.List; import java.util.function.Supplier; /** @@ -63,6 +63,16 @@ public interface TransferHandler extends Comparable<TransferHandler> { return Double.compare(getPriority(), o.getPriority()); } + @Environment(EnvType.CLIENT) + @Nullable + default TransferHandlerErrorRenderer provideErrorRenderer(Context context, Object data) { + if (data instanceof IntList) { + return TransferHandlerErrorRenderer.forRedSlots((IntList) data); + } + + return null; + } + @ApiStatus.NonExtendable interface Result { /** @@ -86,7 +96,7 @@ public interface TransferHandler extends Comparable<TransferHandler> { * @param error The error itself */ static Result createFailed(Component error) { - return new ResultImpl(error, new IntArrayList(), 0x67ff0000); + return new ResultImpl(error, 0x67ff0000); } /** @@ -97,7 +107,7 @@ public interface TransferHandler extends Comparable<TransferHandler> { * @param color A special color for the button */ static Result createFailedCustomButtonColor(Component error, int color) { - return new ResultImpl(error, new IntArrayList(), color); + return createFailed(error).color(color); } /** @@ -107,7 +117,7 @@ public interface TransferHandler extends Comparable<TransferHandler> { * @param redSlots A list of slots to be marked as red. Will be passed to {@link TransferDisplayCategory}. */ static Result createFailed(Component error, IntList redSlots) { - return new ResultImpl(error, redSlots, 0x67ff0000); + return createFailed(error).errorRenderer(redSlots); } /** @@ -119,7 +129,7 @@ public interface TransferHandler extends Comparable<TransferHandler> { * @param redSlots A list of slots to be marked as red. Will be passed to {@link TransferDisplayCategory}. */ static Result createFailedCustomButtonColor(Component error, IntList redSlots, int color) { - return new ResultImpl(error, redSlots, color); + return createFailed(error).errorRenderer(redSlots).color(color); } /** @@ -138,13 +148,18 @@ public interface TransferHandler extends Comparable<TransferHandler> { * @return the color in which the button should be displayed in. */ int getColor(); - + /** * Sets the color in which the button should be displayed in. */ Result color(int color); /** + * Sets the error data, to be passed to {@link TransferHandler#provideErrorRenderer(Context, Object)}. + */ + Result errorRenderer(Object data); + + /** * @return whether this handler has successfully handled the transfer. */ boolean isSuccessful(); @@ -173,10 +188,12 @@ public interface TransferHandler extends Comparable<TransferHandler> { */ Component getError(); - /** - * @return a list of slots to be marked as red. Will be passed to {@link TransferDisplayCategory}. - */ - IntList getIntegers(); + @Environment(EnvType.CLIENT) + @ApiStatus.Internal + TransferHandlerErrorRenderer getErrorRenderer(TransferHandler handler, Context context); + + @ApiStatus.Internal + void fillTooltip(List<Component> components); } @ApiStatus.NonExtendable @@ -211,7 +228,7 @@ public interface TransferHandler extends Comparable<TransferHandler> { final class ResultImpl implements Result { private boolean successful, applicable, returningToScreen, blocking; private Component error; - private IntList integers = new IntArrayList(); + private Object errorRenderer; private int color; private ResultImpl() { @@ -227,12 +244,10 @@ public interface TransferHandler extends Comparable<TransferHandler> { this.applicable = applicable; } - public ResultImpl(Component error, IntList integers, int color) { + public ResultImpl(Component error, int color) { this.successful = false; this.applicable = true; this.error = error; - if (integers != null) - this.integers = integers; this.color = color; } @@ -255,6 +270,12 @@ public interface TransferHandler extends Comparable<TransferHandler> { } @Override + public Result errorRenderer(Object data) { + this.errorRenderer = data; + return this; + } + + @Override public boolean isSuccessful() { return successful; } @@ -280,8 +301,17 @@ public interface TransferHandler extends Comparable<TransferHandler> { } @Override - public IntList getIntegers() { - return integers; + @Environment(EnvType.CLIENT) + public TransferHandlerErrorRenderer getErrorRenderer(TransferHandler handler, Context context) { + if (errorRenderer == null) return null; + return handler.provideErrorRenderer(context, errorRenderer); + } + + @Override + public void fillTooltip(List<Component> components) { + if (!isSuccessful() && isApplicable()) { + components.add(getError()); + } } } diff --git a/api/src/main/java/me/shedaniel/rei/api/client/registry/transfer/TransferHandlerErrorRenderer.java b/api/src/main/java/me/shedaniel/rei/api/client/registry/transfer/TransferHandlerErrorRenderer.java new file mode 100644 index 000000000..d9669d753 --- /dev/null +++ b/api/src/main/java/me/shedaniel/rei/api/client/registry/transfer/TransferHandlerErrorRenderer.java @@ -0,0 +1,54 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.api.client.registry.transfer; + +import com.mojang.blaze3d.vertex.PoseStack; +import it.unimi.dsi.fastutil.ints.IntList; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; +import me.shedaniel.rei.api.client.registry.display.DisplayCategory; +import me.shedaniel.rei.api.client.registry.display.TransferDisplayCategory; +import me.shedaniel.rei.api.common.display.Display; +import org.jetbrains.annotations.ApiStatus; + +import java.util.List; +import java.util.Objects; + +@ApiStatus.Experimental +@FunctionalInterface +public interface TransferHandlerErrorRenderer { + void render(PoseStack matrices, int mouseX, int mouseY, float delta, List<Widget> widgets, Rectangle bounds, Display display); + + @ApiStatus.Internal + static TransferHandlerErrorRenderer forRedSlots(IntList redSlots) { + return (matrices, mouseX, mouseY, delta, widgets, bounds, display) -> { + DisplayCategory<?> category = Objects.requireNonNull(CategoryRegistry.getInstance().get(display.getCategoryIdentifier())) + .getCategory(); + if (category instanceof TransferDisplayCategory) { + ((TransferDisplayCategory<Display>) category).renderRedSlots(matrices, widgets, bounds, display, redSlots); + } + }; + } +} diff --git a/api/src/main/java/me/shedaniel/rei/api/common/display/Display.java b/api/src/main/java/me/shedaniel/rei/api/common/display/Display.java index 1ee2515e3..8b2b2ee22 100644 --- a/api/src/main/java/me/shedaniel/rei/api/common/display/Display.java +++ b/api/src/main/java/me/shedaniel/rei/api/common/display/Display.java @@ -25,6 +25,8 @@ package me.shedaniel.rei.api.common.display; import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.api.common.transfer.info.MenuInfo; +import me.shedaniel.rei.api.common.transfer.info.MenuSerializationContext; import me.shedaniel.rei.impl.display.DisplaySpec; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.ApiStatus; @@ -47,6 +49,10 @@ public interface Display extends DisplaySpec { */ List<EntryIngredient> getInputEntries(); + default List<EntryIngredient> getInputEntries(MenuSerializationContext<?, ?, ?> context, MenuInfo<?, ?> info, boolean fill) { + return getInputEntries(); + } + /** * @return a list of outputs */ diff --git a/api/src/main/java/me/shedaniel/rei/api/common/entry/EntryStack.java b/api/src/main/java/me/shedaniel/rei/api/common/entry/EntryStack.java index 74529de5c..da37a0432 100644 --- a/api/src/main/java/me/shedaniel/rei/api/common/entry/EntryStack.java +++ b/api/src/main/java/me/shedaniel/rei/api/common/entry/EntryStack.java @@ -52,6 +52,7 @@ import java.util.function.Function; /** * @see me.shedaniel.rei.api.common.util.EntryStacks */ +@ApiStatus.NonExtendable public interface EntryStack<T> extends TextRepresentable, Renderer { static EntryStack<Unit> empty() { return Internals.getEntryStackProvider().empty(); @@ -122,14 +123,54 @@ public interface EntryStack<T> extends TextRepresentable, Renderer { boolean isEmpty(); + /** + * Returns a copy of this stack. + * The copied stack will retain the same settings applied, with a copied value. + * + * @return a copy for an entry + */ EntryStack<T> copy(); + /** + * Returns a copy of this stack. + * The copied stack will retain the value object, with no settings applied. + * + * @return a copy for an entry + */ default EntryStack<T> rewrap() { return copy(); } + /** + * Returns a copy of this stack. + * The copied stack will have no settings applied. + * <p> + * The new value should be functionally equivalent to the original value, + * but should have a normalized state. + * <p> + * For example, an {@link net.minecraft.world.item.ItemStack} should have its + * amount removed, but its tags kept. + * + * @return a copy for an entry + */ EntryStack<T> normalize(); + /** + * Returns a copy of this stack. + * The copied stack will have no settings applied. + * <p> + * The new value should be the bare minimum to match the original value. + * <p> + * For example, an {@link net.minecraft.world.item.ItemStack} should have its + * amount and tags removed. + * + * @return a copy for an entry + * @since 6.2 + */ + default EntryStack<T> wildcard() { + return normalize(); + } + Collection<ResourceLocation> getTagsFor(); @Deprecated diff --git a/api/src/main/java/me/shedaniel/rei/api/common/entry/type/EntryDefinition.java b/api/src/main/java/me/shedaniel/rei/api/common/entry/type/EntryDefinition.java index 81e2a93dd..343a3491c 100644 --- a/api/src/main/java/me/shedaniel/rei/api/common/entry/type/EntryDefinition.java +++ b/api/src/main/java/me/shedaniel/rei/api/common/entry/type/EntryDefinition.java @@ -46,22 +46,92 @@ import java.util.Collection; * @see EntryTypeRegistry */ public interface EntryDefinition<T> { + /** + * Returns the type of the entry. + * + * @return the type of the entry + */ Class<T> getValueType(); + /** + * Returns the type of this definition. The type is also used for comparing the type of two definitions, + * as the definition does not guarantee object and reference equality. + * + * @return the type of this definition + */ EntryType<T> getType(); + /** + * Returns the renderer for this entry, this is used to render the entry, and provide tooltip. + * External plugins can extend this method using {@link me.shedaniel.rei.api.client.entry.renderer.EntryRendererRegistry} + * to provide custom renderers. + * + * @return the renderer for this entry + */ @Environment(EnvType.CLIENT) EntryRenderer<T> getRenderer(); + /** + * Returns the identifier for an entry, used in identifier search argument type, + * and appending the mod name to the tooltip. + * + * @param entry the entry + * @param value the value of the entry + * @return the identifier for an entry + */ @Nullable ResourceLocation getIdentifier(EntryStack<T> entry, T value); + /** + * Returns whether the entry is empty, empty entries are not displayed, + * and are considered invalid. + * Empty entries will be treated equally to {@link EntryStack#empty()}. + * + * @param entry the entry + * @param value the value of the entry + * @return whether the entry is empty + */ boolean isEmpty(EntryStack<T> entry, T value); + /** + * Returns a copy for an entry. + * + * @param entry the entry + * @param value the value of the entry + * @return a copy for an entry + */ T copy(EntryStack<T> entry, T value); + /** + * Returns a normalized copy for an entry. + * The returned stack should be functionally equivalent to the original stack, + * but should have a normalized state. + * <p> + * For example, an {@link net.minecraft.world.item.ItemStack} should have its + * amount removed, but its tags kept. + * + * @param entry the entry + * @param value the value of the entry + * @return a normalized copy for an entry + */ T normalize(EntryStack<T> entry, T value); + /** + * Returns a wildcard copy for an entry. + * The returned stack should be the bare minimum to match the original stack. + * <p> + * For example, an {@link net.minecraft.world.item.ItemStack} should have its + * amount and tags removed. + * + * @param entry the entry + * @param value the value of the entry + * @return a wildcard copy for an entry + * @since 6.2 + */ + default T wildcard(EntryStack<T> entry, T value) { + return normalize(entry, value); + } + long hash(EntryStack<T> entry, T value, ComparisonContext context); boolean equals(T o1, T o2, ComparisonContext context); diff --git a/api/src/main/java/me/shedaniel/rei/api/common/transfer/info/MenuInfo.java b/api/src/main/java/me/shedaniel/rei/api/common/transfer/info/MenuInfo.java index cde2c2231..7fe0e15a4 100644 --- a/api/src/main/java/me/shedaniel/rei/api/common/transfer/info/MenuInfo.java +++ b/api/src/main/java/me/shedaniel/rei/api/common/transfer/info/MenuInfo.java @@ -23,6 +23,12 @@ package me.shedaniel.rei.api.common.transfer.info; +import com.mojang.blaze3d.vertex.PoseStack; +import it.unimi.dsi.fastutil.ints.IntList; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.gui.widgets.Slot; +import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.display.DisplaySerializerRegistry; import me.shedaniel.rei.api.common.entry.EntryStack; @@ -35,10 +41,12 @@ import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessor; import me.shedaniel.rei.api.common.util.CollectionUtils; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.client.gui.GuiComponent; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.ApiStatus; import java.util.Collections; import java.util.List; @@ -112,16 +120,30 @@ public interface MenuInfo<T extends AbstractContainerMenu, D extends Display> { * Returns the inputs of the {@link Display}. The nested lists are possible stacks for that specific slot. * * @param context the context of the transfer + * @param fill whether this call is for a fill or not, if it is for a fill, the returned list should be aligned for the menu, + * otherwise it should be aligned for the display category * @return the list of lists of items */ - default List<List<ItemStack>> getInputs(MenuInfoContext<T, ?, D> context) { + default List<List<ItemStack>> getInputs(MenuInfoContext<T, ?, D> context, boolean fill) { if (context.getDisplay() == null) return Collections.emptyList(); - return CollectionUtils.map(context.getDisplay().getInputEntries(), inputEntry -> + return CollectionUtils.map(context.getDisplay().getInputEntries(context, this, fill), inputEntry -> CollectionUtils.<EntryStack<?>, ItemStack>filterAndMap(inputEntry, stack -> stack.getType() == VanillaEntryTypes.ITEM, EntryStack::castValue)); } /** + * Returns the inputs of the {@link Display}. The nested lists are possible stacks for that specific slot. + * + * @param context the context of the transfer + * @return the list of lists of items + */ + @Deprecated + @ApiStatus.ScheduledForRemoval + default List<List<ItemStack>> getInputs(MenuInfoContext<T, ?, D> context) { + return getInputs(context, false); + } + + /** * Serializes the {@link Display} as {@link CompoundTag}, sent to the server for further info for the transfer. * * @param context the context of the transfer @@ -138,4 +160,35 @@ public interface MenuInfo<T extends AbstractContainerMenu, D extends Display> { * @return the {@link Display} */ D getDisplay(); + + /** + * Renders the missing ingredients of the transfer. + * The indices of the missing stacks are provided, this aligns with the list returned by {@link #getInputs(MenuInfoContext, boolean)}. + * + * @param context the context of the transfer + * @param inputs the list of inputs + * @param missingIndices the indices of the missing stacks + * @param matrices the rendering transforming matrices + * @param mouseX the mouse x position + * @param mouseY the mouse y position + * @param delta the delta frame time + * @param widgets the widgets set-up by the category + * @param bounds the bounds of the display + */ + @Environment(EnvType.CLIENT) + default void renderMissingInput(MenuInfoContext<T, ?, D> context, List<List<ItemStack>> inputs, IntList missingIndices, PoseStack matrices, int mouseX, int mouseY, + float delta, List<Widget> widgets, Rectangle bounds) { + int i = 0; + for (Widget widget : widgets) { + if (widget instanceof Slot && ((Slot) widget).getNoticeMark() == Slot.INPUT) { + if (missingIndices.contains(i++)) { + matrices.pushPose(); + matrices.translate(0, 0, 400); + Rectangle innerBounds = ((Slot) widget).getInnerBounds(); + GuiComponent.fill(matrices, innerBounds.x, innerBounds.y, innerBounds.getMaxX(), innerBounds.getMaxY(), 0x40ff0000); + matrices.popPose(); + } + } + } + } } diff --git a/api/src/main/java/me/shedaniel/rei/api/common/util/CollectionUtils.java b/api/src/main/java/me/shedaniel/rei/api/common/util/CollectionUtils.java index aa4ac5037..946d9336f 100644 --- a/api/src/main/java/me/shedaniel/rei/api/common/util/CollectionUtils.java +++ b/api/src/main/java/me/shedaniel/rei/api/common/util/CollectionUtils.java @@ -265,6 +265,19 @@ public class CollectionUtils { return l == null ? Collections.emptyList() : l; } + public static <T, R> List<R> mapAndFilter(Iterable<T> list, Predicate<R> predicate, Function<T, R> function) { + List<R> l = null; + for (T t : list) { + R r = function.apply(t); + if (predicate.test(r)) { + if (l == null) + l = Lists.newArrayList(); + l.add(r); + } + } + return l == null ? Collections.emptyList() : l; + } + public static <T> int sumInt(Iterable<T> list, Function<T, Integer> function) { int sum = 0; for (T t : list) { @@ -323,6 +336,29 @@ public class CollectionUtils { public int size() { return realSize; } + + @Override + public Iterator<T> iterator() { + Iterator<T> iterator = super.iterator(); + return new Iterator<T>() { + boolean endReached = false; + + @Override + public boolean hasNext() { + return iterator.hasNext() && !endReached; + } + + @Override + public T next() { + try { + return iterator.next(); + } catch (NoSuchElementException e) { + endReached = true; + return null; + } + } + }; + } }; } }; |
