mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-30 00:56:56 +13:00
More shell rework
This commit is contained in:
parent
11667d7876
commit
4bb7627488
21 changed files with 79 additions and 99 deletions
|
@ -136,7 +136,7 @@ run {
|
||||||
systemProperty 'io.xpipe.app.writeLogs', "true"
|
systemProperty 'io.xpipe.app.writeLogs', "true"
|
||||||
systemProperty 'io.xpipe.app.writeSysOut', "true"
|
systemProperty 'io.xpipe.app.writeSysOut', "true"
|
||||||
systemProperty 'io.xpipe.app.developerMode', "true"
|
systemProperty 'io.xpipe.app.developerMode', "true"
|
||||||
systemProperty 'io.xpipe.app.logLevel', "debug"
|
systemProperty 'io.xpipe.app.logLevel', "trace"
|
||||||
systemProperty 'io.xpipe.app.fullVersion', rootProject.fullVersion
|
systemProperty 'io.xpipe.app.fullVersion', rootProject.fullVersion
|
||||||
// systemProperty "io.xpipe.beacon.port", "21724"
|
// systemProperty "io.xpipe.beacon.port", "21724"
|
||||||
// systemProperty "io.xpipe.beacon.printMessages", "true"
|
// systemProperty "io.xpipe.beacon.printMessages", "true"
|
||||||
|
|
|
@ -209,7 +209,7 @@ public class FileBrowserComp extends SimpleComp {
|
||||||
? DataStorage.get()
|
? DataStorage.get()
|
||||||
.getStoreEntry(model.getStore().getValue())
|
.getStoreEntry(model.getStore().getValue())
|
||||||
.getProvider()
|
.getProvider()
|
||||||
.getDisplayIconFileName()
|
.getDisplayIconFileName(model.getStore().getValue())
|
||||||
: null;
|
: null;
|
||||||
},
|
},
|
||||||
model.getStore());
|
model.getStore());
|
||||||
|
|
|
@ -204,9 +204,9 @@ final class FileListComp extends AnchorPane {
|
||||||
}
|
}
|
||||||
newItems.addAll(newValue);
|
newItems.addAll(newValue);
|
||||||
table.getItems().setAll(newItems);
|
table.getItems().setAll(newItems);
|
||||||
if (newValue.size() > 0) {
|
// if (newValue.size() > 0) {
|
||||||
table.scrollTo(0);
|
// table.scrollTo(0);
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.core.impl.FileNames;
|
import io.xpipe.core.impl.FileNames;
|
||||||
import io.xpipe.core.impl.LocalStore;
|
import io.xpipe.core.impl.LocalStore;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
import io.xpipe.core.store.ConnectionFileSystem;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -13,7 +14,25 @@ import java.util.List;
|
||||||
|
|
||||||
public class FileSystemHelper {
|
public class FileSystemHelper {
|
||||||
|
|
||||||
public static String normalizeDirectoryPath(OpenFileSystemModel model, String path) {
|
public static String getStartDirectory(OpenFileSystemModel model) throws Exception {
|
||||||
|
// Handle special case when file system creation has failed
|
||||||
|
if (model.getFileSystem() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionFileSystem fileSystem = (ConnectionFileSystem) model.getFileSystem();
|
||||||
|
var current = !(model.getStore().getValue() instanceof LocalStore)
|
||||||
|
? fileSystem
|
||||||
|
.getShellControl()
|
||||||
|
.executeStringSimpleCommand(fileSystem
|
||||||
|
.getShellControl()
|
||||||
|
.getShellDialect()
|
||||||
|
.getPrintWorkingDirectoryCommand())
|
||||||
|
: fileSystem.getShell().get().getOsType().getHomeDirectory(fileSystem.getShell().get());
|
||||||
|
return FileSystemHelper.normalizeDirectoryPath(model, current);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalizeDirectoryPath(OpenFileSystemModel model, String path) throws Exception {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +56,11 @@ public class FileSystemHelper {
|
||||||
return path + "\\";
|
return path + "\\";
|
||||||
}
|
}
|
||||||
|
|
||||||
return FileNames.toDirectory(path);
|
var normalized = shell.get()
|
||||||
|
.getShellDialect()
|
||||||
|
.normalizeDirectory(shell.get(), path)
|
||||||
|
.readOrThrow();
|
||||||
|
return FileNames.toDirectory(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSystem.FileEntry getLocal(Path file) throws Exception {
|
public static FileSystem.FileEntry getLocal(Path file) throws Exception {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import io.xpipe.app.util.BusyProperty;
|
||||||
import io.xpipe.app.util.TerminalHelper;
|
import io.xpipe.app.util.TerminalHelper;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.impl.FileNames;
|
import io.xpipe.core.impl.FileNames;
|
||||||
import io.xpipe.core.impl.LocalStore;
|
|
||||||
import io.xpipe.core.store.ConnectionFileSystem;
|
import io.xpipe.core.store.ConnectionFileSystem;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import io.xpipe.core.store.FileSystemStore;
|
import io.xpipe.core.store.FileSystemStore;
|
||||||
|
@ -76,7 +75,14 @@ final class OpenFileSystemModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<String> cd(String path) {
|
public Optional<String> cd(String path) {
|
||||||
var newPath = FileSystemHelper.normalizeDirectoryPath(this, path);
|
String newPath = null;
|
||||||
|
try {
|
||||||
|
newPath = FileSystemHelper.normalizeDirectoryPath(this, path);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
|
return Optional.of(currentPath.get());
|
||||||
|
}
|
||||||
|
|
||||||
if (!Objects.equals(path, newPath)) {
|
if (!Objects.equals(path, newPath)) {
|
||||||
return Optional.of(newPath);
|
return Optional.of(newPath);
|
||||||
}
|
}
|
||||||
|
@ -96,7 +102,8 @@ final class OpenFileSystemModel {
|
||||||
this.fileSystem = fs;
|
this.fileSystem = fs;
|
||||||
}
|
}
|
||||||
|
|
||||||
path = FileSystemHelper.normalizeDirectoryPath(this, path);
|
// Assumed that the path is normalized to improve performance!
|
||||||
|
// path = FileSystemHelper.normalizeDirectoryPath(this, path);
|
||||||
|
|
||||||
navigateToSync(path);
|
navigateToSync(path);
|
||||||
filter.setValue(null);
|
filter.setValue(null);
|
||||||
|
@ -218,14 +225,7 @@ final class OpenFileSystemModel {
|
||||||
fs.open();
|
fs.open();
|
||||||
this.fileSystem = fs;
|
this.fileSystem = fs;
|
||||||
|
|
||||||
var current = !(fileSystem instanceof LocalStore) && fs instanceof ConnectionFileSystem connectionFileSystem
|
var current = FileSystemHelper.getStartDirectory(this);
|
||||||
? connectionFileSystem
|
|
||||||
.getShellControl()
|
|
||||||
.executeStringSimpleCommand(connectionFileSystem
|
|
||||||
.getShellControl()
|
|
||||||
.getShellDialect()
|
|
||||||
.getPrintWorkingDirectoryCommand())
|
|
||||||
: null;
|
|
||||||
cdSync(current);
|
cdSync(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class DataStoreSelectorComp extends Comp<CompStructure<Button>> {
|
||||||
? DataStoreProviders.byStoreClass(chosenStore.getValue().getClass())
|
? DataStoreProviders.byStoreClass(chosenStore.getValue().getClass())
|
||||||
.orElse(null)
|
.orElse(null)
|
||||||
: null;
|
: null;
|
||||||
var graphic = provider != null ? provider.getDisplayIconFileName() : "file_icon.png";
|
var graphic = provider != null ? provider.getDisplayIconFileName(chosenStore.getValue()) : "file_icon.png";
|
||||||
if (chosenStore.getValue() == null || !(chosenStore.getValue() instanceof FileStore f)) {
|
if (chosenStore.getValue() == null || !(chosenStore.getValue() instanceof FileStore f)) {
|
||||||
return JfxHelper.createNamedEntry(
|
return JfxHelper.createNamedEntry(
|
||||||
AppI18n.get("selectStreamStore"), AppI18n.get("openStreamStoreWizard"), graphic);
|
AppI18n.get("selectStreamStore"), AppI18n.get("openStreamStoreWizard"), graphic);
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class DsStoreProviderChoiceComp extends Comp<CompStructure<ComboBox<Node>
|
||||||
return createDefaultNode();
|
return createDefaultNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
var graphic = provider.getDisplayIconFileName();
|
var graphic = provider.getDisplayIconFileName(null);
|
||||||
return JfxHelper.createNamedEntry(provider.getDisplayName(), provider.getDisplayDescription(), graphic);
|
return JfxHelper.createNamedEntry(provider.getDisplayName(), provider.getDisplayDescription(), graphic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@ public class NamedStoreChoiceComp extends SimpleComp implements Validatable {
|
||||||
|
|
||||||
var view = new ListViewComp<>(shown, list, prop, (DataStoreEntry e) -> {
|
var view = new ListViewComp<>(shown, list, prop, (DataStoreEntry e) -> {
|
||||||
var provider = e.getProvider();
|
var provider = e.getProvider();
|
||||||
var graphic = provider.getDisplayIconFileName();
|
var graphic = provider.getDisplayIconFileName(e.getStore());
|
||||||
var top = String.format("%s (%s)", e.getName(), provider.getDisplayName());
|
var top = String.format("%s (%s)", e.getName(), provider.getDisplayName());
|
||||||
var bottom = provider.toSummaryString(e.getStore(), 50);
|
var bottom = provider.toSummaryString(e.getStore(), 50);
|
||||||
var el = JfxHelper.createNamedEntry(top, bottom, graphic);
|
var el = JfxHelper.createNamedEntry(top, bottom, graphic);
|
||||||
|
|
|
@ -98,7 +98,7 @@ public class StoreEntryComp extends SimpleComp {
|
||||||
private Node createIcon() {
|
private Node createIcon() {
|
||||||
var img = entry.isDisabled()
|
var img = entry.isDisabled()
|
||||||
? "disabled_icon.png"
|
? "disabled_icon.png"
|
||||||
: entry.getEntry().getProvider().getDisplayIconFileName();
|
: entry.getEntry().getProvider().getDisplayIconFileName(entry.getEntry().getStore());
|
||||||
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), 55, 45);
|
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), 55, 45);
|
||||||
var storeIcon = imageComp.createRegion();
|
var storeIcon = imageComp.createRegion();
|
||||||
storeIcon.getStyleClass().add("icon");
|
storeIcon.getStyleClass().add("icon");
|
||||||
|
|
|
@ -41,7 +41,9 @@ public class StoreEntryFlatMiniSection extends SimpleComp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var image = entry.getState() == DataStoreEntry.State.LOAD_FAILED ? "disabled_icon.png" : entry.getProvider().getDisplayIconFileName();
|
var image = entry.getState() == DataStoreEntry.State.LOAD_FAILED
|
||||||
|
? "disabled_icon.png"
|
||||||
|
: entry.getProvider().getDisplayIconFileName(entry.getStore());
|
||||||
var label = new Label(entry.getName(), new PrettyImageComp(new SimpleStringProperty(image), 20, 20).createRegion());
|
var label = new Label(entry.getName(), new PrettyImageComp(new SimpleStringProperty(image), 20, 20).createRegion());
|
||||||
var spacer = new Spacer(depth * 10, Orientation.HORIZONTAL);
|
var spacer = new Spacer(depth * 10, Orientation.HORIZONTAL);
|
||||||
var box = new HBox(spacer, label);
|
var box = new HBox(spacer, label);
|
||||||
|
|
|
@ -100,7 +100,7 @@ public interface DataStoreProvider {
|
||||||
return i != -1 ? n.substring(i + 1) : n;
|
return i != -1 ? n.substring(i + 1) : n;
|
||||||
}
|
}
|
||||||
|
|
||||||
default String getDisplayIconFileName() {
|
default String getDisplayIconFileName(DataStore store) {
|
||||||
return getModuleName() + ":" + getId() + "_icon.png";
|
return getModuleName() + ":" + getId() + "_icon.png";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,13 +34,13 @@ public class FileSystemStoreChoiceComp extends SimpleComp {
|
||||||
|
|
||||||
private Region createGraphic(FileSystemStore s) {
|
private Region createGraphic(FileSystemStore s) {
|
||||||
var provider = DataStoreProviders.byStore(s);
|
var provider = DataStoreProviders.byStore(s);
|
||||||
var img = new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName()), 16, 16);
|
var img = new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName(s)), 16, 16);
|
||||||
return new Label(getName(s), img.createRegion());
|
return new Label(getName(s), img.createRegion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Region createDisplayGraphic(FileSystemStore s) {
|
private Region createDisplayGraphic(FileSystemStore s) {
|
||||||
var provider = DataStoreProviders.byStore(s);
|
var provider = DataStoreProviders.byStore(s);
|
||||||
var img = new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName()), 16, 16);
|
var img = new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName(s)), 16, 16);
|
||||||
return new Label(null, img.createRegion());
|
return new Label(null, img.createRegion());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class ShellStoreChoiceComp<T extends ShellStore> extends SimpleComp {
|
||||||
protected Region createGraphic(T s) {
|
protected Region createGraphic(T s) {
|
||||||
var provider = DataStoreProviders.byStore(s);
|
var provider = DataStoreProviders.byStore(s);
|
||||||
var imgView =
|
var imgView =
|
||||||
new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName()), 16, 16).createRegion();
|
new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName(s)), 16, 16).createRegion();
|
||||||
|
|
||||||
var name = DataStorage.get().getUsableStores().stream()
|
var name = DataStorage.get().getUsableStores().stream()
|
||||||
.filter(e -> e.equals(s))
|
.filter(e -> e.equals(s))
|
||||||
|
|
|
@ -3,6 +3,9 @@ package io.xpipe.core.charsetter;
|
||||||
import io.xpipe.core.util.Identifiers;
|
import io.xpipe.core.util.Identifiers;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -117,6 +120,17 @@ public class StreamCharset {
|
||||||
byte[] byteOrderMark;
|
byte[] byteOrderMark;
|
||||||
List<String> names;
|
List<String> names;
|
||||||
|
|
||||||
|
public Reader reader(InputStream stream) throws Exception {
|
||||||
|
if (hasByteOrderMark()) {
|
||||||
|
var bom = stream.readNBytes(getByteOrderMark().length);
|
||||||
|
if (bom.length != 0 && !Arrays.equals(bom, getByteOrderMark())) {
|
||||||
|
throw new IllegalStateException("Charset does not match: " + charset.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new InputStreamReader(stream, charset);
|
||||||
|
}
|
||||||
|
|
||||||
public static StreamCharset get(Charset charset, boolean byteOrderMark) {
|
public static StreamCharset get(Charset charset, boolean byteOrderMark) {
|
||||||
return ALL.stream()
|
return ALL.stream()
|
||||||
.filter(streamCharset ->
|
.filter(streamCharset ->
|
||||||
|
|
|
@ -9,6 +9,8 @@ import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
public interface ProcessControl extends Closeable, AutoCloseable {
|
public interface ProcessControl extends Closeable, AutoCloseable {
|
||||||
|
|
||||||
|
void resetData();
|
||||||
|
|
||||||
ExecutorService getStdoutReader();
|
ExecutorService getStdoutReader();
|
||||||
|
|
||||||
ExecutorService getStderrReader();
|
ExecutorService getStderrReader();
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.core.process;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import io.xpipe.core.charsetter.NewLine;
|
import io.xpipe.core.charsetter.NewLine;
|
||||||
|
import io.xpipe.core.charsetter.StreamCharset;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -13,6 +14,12 @@ import java.util.stream.Stream;
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||||
public interface ShellDialect {
|
public interface ShellDialect {
|
||||||
|
|
||||||
|
default StreamCharset getTextFileCharset(ShellControl sc) {
|
||||||
|
return StreamCharset.get(sc.getCharset(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandControl normalizeDirectory(ShellControl shellControl, String directory);
|
||||||
|
|
||||||
String fileArgument(String s);
|
String fileArgument(String s);
|
||||||
|
|
||||||
default String applyRcFileCommand() {
|
default String applyRcFileCommand() {
|
||||||
|
@ -91,6 +98,10 @@ public interface ShellDialect {
|
||||||
|
|
||||||
String prepareInitFileOpenCommand(ShellControl parent, String file);
|
String prepareInitFileOpenCommand(ShellControl parent, String file);
|
||||||
|
|
||||||
|
String runScript(String file);
|
||||||
|
|
||||||
|
String sourceScript(String file);
|
||||||
|
|
||||||
String executeCommandWithShell(String cmd);
|
String executeCommandWithShell(String cmd);
|
||||||
|
|
||||||
String getMkdirsCommand(String dirs);
|
String getMkdirsCommand(String dirs);
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
package io.xpipe.ext.base;
|
|
||||||
|
|
||||||
import io.xpipe.app.ext.DataStoreProvider;
|
|
||||||
import io.xpipe.app.storage.DataStorage;
|
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
|
||||||
import io.xpipe.app.storage.StorageElement;
|
|
||||||
import io.xpipe.core.impl.LocalStore;
|
|
||||||
import io.xpipe.core.process.OsType;
|
|
||||||
import io.xpipe.core.store.DataStore;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class LocalStoreProvider implements DataStoreProvider {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String queryInformationString(DataStore store, int length) throws Exception {
|
|
||||||
try (var pc = LocalStore.getShell().start()) {
|
|
||||||
return OsType.getLocal().determineOperatingSystemName(pc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toSummaryString(DataStore store, int length) {
|
|
||||||
return "localhost";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldShow() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void storageInit() throws Exception {
|
|
||||||
var hasLocal = DataStorage.get().getUsableStores().stream()
|
|
||||||
.anyMatch(dataStore -> dataStore instanceof LocalStore);
|
|
||||||
if (hasLocal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var e = DataStoreEntry.createNew(UUID.randomUUID(), "Local Machine", new LocalStore());
|
|
||||||
DataStorage.get().addStoreEntry(e);
|
|
||||||
e.setConfiguration(StorageElement.Configuration.builder()
|
|
||||||
.deletable(false)
|
|
||||||
.editable(false)
|
|
||||||
.refreshable(true)
|
|
||||||
.renameable(false)
|
|
||||||
.build());
|
|
||||||
e.refresh(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DataStore defaultStore() {
|
|
||||||
return new LocalStore();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDisplayName() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getPossibleNames() {
|
|
||||||
return List.of("local", "localhost");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Class<?>> getStoreClasses() {
|
|
||||||
return List.of(LocalStore.class);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -44,7 +44,6 @@ open module io.xpipe.ext.base {
|
||||||
provides DataStoreProvider with
|
provides DataStoreProvider with
|
||||||
SinkDrainStoreProvider,
|
SinkDrainStoreProvider,
|
||||||
HttpStoreProvider,
|
HttpStoreProvider,
|
||||||
LocalStoreProvider,
|
|
||||||
InternalStreamProvider,
|
InternalStreamProvider,
|
||||||
FileStoreProvider,
|
FileStoreProvider,
|
||||||
InMemoryStoreProvider;
|
InMemoryStoreProvider;
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Loading…
Reference in a new issue