feat: improve project icon changing experience

This commit is contained in:
Jake Potrebic 2022-12-24 16:26:52 -08:00
parent 309b9f278f
commit d6d9319fc4
No known key found for this signature in database
GPG Key ID: 27CC63F7CBC866C7
11 changed files with 197 additions and 138 deletions

View File

@ -9,6 +9,7 @@ trim_trailing_whitespace = true
insert_final_newline = true
ij_any_block_comment_at_first_column = false
ij_any_line_comment_at_first_column = false
ij_any_line_comment_add_space_on_reformat = true
ij_any_block_comment_add_space = true
max_line_length = 160
ij_visual_guides = 160
@ -19,8 +20,11 @@ ij_html_quote_style = double
[*.java]
ij_java_insert_inner_class_imports = false
ij_java_use_fq_class_names = false
ij_java_use_single_class_imports = true
ij_java_class_count_to_use_import_on_demand = 99999
ij_java_names_count_to_use_import_on_demand = 99999
ij_java_keep_simple_lambdas_in_one_line = true
ij_java_keep_simple_classes_in_one_line = true
ij_java_imports_layout = *,|,$*
ij_java_generate_final_locals = true
ij_java_generate_final_parameters = true

6
.gitignore vendored
View File

@ -18,7 +18,7 @@ work/
.sts4-cache
### IntelliJ IDEA ###
.idea
.idea/*
*.iws
*.iml
*.ipr
@ -33,3 +33,7 @@ build/
### VS Code ###
.vscode/
### Unignore Inspection Profiles ###
!/.idea/inspectionProfiles

View File

@ -0,0 +1,58 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="BoundedWildcard" enabled="true" level="WARNING" enabled_by_default="true">
<option name="REPORT_INVARIANT_CLASSES" value="false" />
</inspection_tool>
<inspection_tool class="ClassInitializer" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CommentedOutCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="minLines" value="8" />
</inspection_tool>
<inspection_tool class="DeclareCollectionAsInterface" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreLocalVariables" value="false" />
<option name="ignorePrivateMethodsAndFields" value="false" />
</inspection_tool>
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExtendsUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
<option name="REPORT_VARIABLES" value="true" />
<option name="REPORT_PARAMETERS" value="true" />
</inspection_tool>
<inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_requireAnnotationsFirst" value="true" />
</inspection_tool>
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreRequiredObsoleteCollectionTypes" value="true" />
</inspection_tool>
<inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemOutErr" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreStaticFieldAccesses" value="false" />
<option name="m_ignoreStaticMethodCalls" value="false" />
<option name="m_ignoreStaticAccessFromStaticContext" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnnotations" value="true" />
</inspection_tool>
<inspection_tool class="UnnecessarySuperConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessarySuperQualifier" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnqualifiedFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnqualifiedInnerClassAccess" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreReferencesToLocalInnerClasses" value="true" />
</inspection_tool>
<inspection_tool class="UnqualifiedMethodAccess" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignorableAnnotations">
<value />
</option>
<option name="ignoreClassesWithOnlyMain" value="false" />
</inspection_tool>
</profile>
</component>

View File

@ -32,8 +32,9 @@ public class HangarProject extends Project implements Joinable<ProjectRoleTable>
private final Collection<HangarProjectPage> pages;
private final List<PinnedVersion> pinnedVersions;
private final Map<Platform, HangarVersion> mainChannelVersions;
private final boolean customIcon;
public HangarProject(final Project project, final long id, final ProjectOwner owner, final List<JoinableMember<ProjectRoleTable>> members, final String lastVisibilityChangeComment, final String lastVisibilityChangeUserName, final HangarProjectInfo info, final Collection<HangarProjectPage> pages, final List<PinnedVersion> pinnedVersions, final Map<Platform, HangarVersion> mainChannelVersions) {
public HangarProject(final Project project, final long id, final ProjectOwner owner, final List<JoinableMember<ProjectRoleTable>> members, final String lastVisibilityChangeComment, final String lastVisibilityChangeUserName, final HangarProjectInfo info, final Collection<HangarProjectPage> pages, final List<PinnedVersion> pinnedVersions, final Map<Platform, HangarVersion> mainChannelVersions, final boolean customIcon) {
super(project);
this.id = id;
this.owner = owner;
@ -44,20 +45,21 @@ public class HangarProject extends Project implements Joinable<ProjectRoleTable>
this.pages = pages;
this.pinnedVersions = pinnedVersions;
this.mainChannelVersions = mainChannelVersions;
this.customIcon = customIcon;
}
public long getId() {
return id;
return this.id;
}
@Override
public long getProjectId() {
return id;
return this.id;
}
@Override
public ProjectOwner getOwner() {
return owner;
return this.owner;
}
@Override
@ -67,23 +69,23 @@ public class HangarProject extends Project implements Joinable<ProjectRoleTable>
@Override
public List<JoinableMember<ProjectRoleTable>> getMembers() {
return members;
return this.members;
}
public String getLastVisibilityChangeComment() {
return lastVisibilityChangeComment;
return this.lastVisibilityChangeComment;
}
public String getLastVisibilityChangeUserName() {
return lastVisibilityChangeUserName;
return this.lastVisibilityChangeUserName;
}
public HangarProjectInfo getInfo() {
return info;
return this.info;
}
public Collection<HangarProjectPage> getPages() {
return pages;
return this.pages;
}
public List<PinnedVersion> getPinnedVersions() {
@ -91,7 +93,7 @@ public class HangarProject extends Project implements Joinable<ProjectRoleTable>
}
public Map<Platform, HangarVersion> getMainChannelVersions() {
return mainChannelVersions;
return this.mainChannelVersions;
}
@Override
@ -105,9 +107,14 @@ public class HangarProject extends Project implements Joinable<ProjectRoleTable>
", info=" + this.info +
", pages=" + this.pages +
", pinnedVersions=" + this.pinnedVersions +
", customIcon=" + this.customIcon +
"} " + super.toString();
}
public boolean isCustomIcon() {
return customIcon;
}
public static class HangarProjectInfo {
private final int publicVersions;
@ -125,35 +132,35 @@ public class HangarProject extends Project implements Joinable<ProjectRoleTable>
}
public int getPublicVersions() {
return publicVersions;
return this.publicVersions;
}
@RequiresPermission(NamedPermission.MOD_NOTES_AND_FLAGS)
public int getFlagCount() {
return flagCount;
return this.flagCount;
}
@RequiresPermission(NamedPermission.MOD_NOTES_AND_FLAGS)
public int getNoteCount() {
return noteCount;
return this.noteCount;
}
public long getStarCount() {
return starCount;
return this.starCount;
}
public long getWatcherCount() {
return watcherCount;
return this.watcherCount;
}
@Override
public String toString() {
return "HangarProjectInfo{" +
"publicVersions=" + publicVersions +
", flagCount=" + flagCount +
", noteCount=" + noteCount +
", starCount=" + starCount +
", watcherCount=" + watcherCount +
"publicVersions=" + this.publicVersions +
", flagCount=" + this.flagCount +
", noteCount=" + this.noteCount +
", starCount=" + this.starCount +
", watcherCount=" + this.watcherCount +
'}';
}
}
@ -167,7 +174,7 @@ public class HangarProject extends Project implements Joinable<ProjectRoleTable>
private final Map<Platform, String> platformDependenciesFormatted;
private final Map<Platform, PlatformVersionDownload> downloads;
public PinnedVersion(long versionId, Type type, String name, @Nested("pc") ProjectChannel channel) {
public PinnedVersion(final long versionId, final Type type, final String name, @Nested("pc") final ProjectChannel channel) {
this.versionId = versionId;
this.type = type;
this.name = name;
@ -177,38 +184,38 @@ public class HangarProject extends Project implements Joinable<ProjectRoleTable>
}
public long getVersionId() {
return versionId;
return this.versionId;
}
public Type getType() {
return type;
return this.type;
}
public String getName() {
return name;
return this.name;
}
public Map<Platform, String> getPlatformDependenciesFormatted() {
return platformDependenciesFormatted;
return this.platformDependenciesFormatted;
}
public ProjectChannel getChannel() {
return channel;
return this.channel;
}
public Map<Platform, PlatformVersionDownload> getDownloads() {
return downloads;
return this.downloads;
}
@Override
public String toString() {
return "PinnedVersion{" +
"versionId=" + versionId +
", type=" + type +
", name='" + name + '\'' +
", channel=" + channel +
", platformDependenciesFormatted=" + platformDependenciesFormatted +
", downloads=" + downloads +
"versionId=" + this.versionId +
", type=" + this.type +
", name='" + this.name + '\'' +
", channel=" + this.channel +
", platformDependenciesFormatted=" + this.platformDependenciesFormatted +
", downloads=" + this.downloads +
'}';
}

View File

@ -21,49 +21,49 @@ public class LocalStorageFileService implements FileService {
private final StorageConfig config;
private final HangarConfig hangarConfig;
public LocalStorageFileService(StorageConfig config, HangarConfig hangarConfig) {
public LocalStorageFileService(final StorageConfig config, final HangarConfig hangarConfig) {
this.config = config;
this.hangarConfig = hangarConfig;
}
@Override
public Resource getResource(String path) {
public Resource getResource(final String path) {
return new FileSystemResource(path);
}
@Override
public boolean exists(String path) {
public boolean exists(final String path) {
return Files.exists(Path.of(path));
}
@Override
public void deleteDirectory(String dir) {
public void deleteDirectory(final String dir) {
FileUtils.deleteDirectory(Path.of(dir));
}
@Override
public boolean delete(String path) {
public boolean delete(final String path) {
return FileUtils.delete(Path.of(path));
}
@Override
public byte[] bytes(String path) throws IOException {
public byte[] bytes(final String path) throws IOException {
return Files.readAllBytes(Path.of(path));
}
@Override
public void write(InputStream inputStream, String path) throws IOException {
Path p = Path.of(path);
public void write(final InputStream inputStream, final String path) throws IOException {
final Path p = Path.of(path);
if (Files.notExists(p)) {
Files.createDirectories(p.getParent());
}
Files.copy(inputStream, p);
Files.copy(inputStream, p, StandardCopyOption.REPLACE_EXISTING);
}
@Override
public void move(String oldPathString, String newPathString) throws IOException {
Path oldPath = Path.of(oldPathString);
Path newPath = Path.of(newPathString);
public void move(final String oldPathString, final String newPathString) throws IOException {
final Path oldPath = Path.of(oldPathString);
final Path newPath = Path.of(newPathString);
if (Files.notExists(newPath)) {
Files.createDirectories(newPath.getParent());
}
@ -75,9 +75,9 @@ public class LocalStorageFileService implements FileService {
}
@Override
public void link(String existingPathString, String newPathString) throws IOException {
Path existingPath = Path.of(existingPathString);
Path newPath = Path.of(newPathString);
public void link(final String existingPathString, final String newPathString) throws IOException {
final Path existingPath = Path.of(existingPathString);
final Path newPath = Path.of(newPathString);
if (Files.notExists(newPath)) {
Files.createDirectories(newPath.getParent());
}
@ -85,17 +85,17 @@ public class LocalStorageFileService implements FileService {
}
@Override
public String resolve(String path, String fileName) {
public String resolve(final String path, final String fileName) {
return Path.of(path).resolve(fileName).toString();
}
@Override
public String getRoot() {
return config.workDir();
return this.config.workDir();
}
@Override
public String getDownloadUrl(String user, String project, String version, Platform platform, String fileName) {
return hangarConfig.getBaseUrl() + "/api/v1/projects/" + user + "/" + project + "/versions/" + version + "/" + platform.name() + "/download";
public String getDownloadUrl(final String user, final String project, final String version, final Platform platform, final String fileName) {
return this.hangarConfig.getBaseUrl() + "/api/v1/projects/" + user + "/" + project + "/versions/" + version + "/" + platform.name() + "/download";
}
}

View File

@ -154,7 +154,7 @@ public class ProjectService extends HangarComponent {
}
}
return new HangarProject(project.getRight(), project.getLeft(), projectOwner, members, lastVisibilityChangeComment, lastVisibilityChangeUserName, info, pages.values(), pinnedVersions, mainChannelVersions);
return new HangarProject(project.getRight(), project.getLeft(), projectOwner, members, lastVisibilityChangeComment, lastVisibilityChangeUserName, info, pages.values(), pinnedVersions, mainChannelVersions, this.fileService.exists(this.projectFiles.getIconPath(author, slug)));
}
public @Nullable HangarVersion getLastVersion(String author, String slug, Platform platform, @Nullable String channel) {

View File

@ -4,16 +4,13 @@ import io.papermc.hangar.HangarComponent;
import io.papermc.hangar.exceptions.HangarApiException;
import io.papermc.hangar.exceptions.InternalHangarException;
import io.papermc.hangar.service.internal.file.FileService;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@Service
public class ImageService extends HangarComponent {
@ -21,27 +18,25 @@ public class ImageService extends HangarComponent {
private final FileService fileService;
@Autowired
public ImageService(ProjectFiles projectFiles, FileService fileService) {
public ImageService(final ProjectFiles projectFiles, final FileService fileService) {
this.projectFiles = projectFiles;
this.fileService = fileService;
}
public ResponseEntity<byte[]> getProjectIcon(String author, String slug) {
String iconPath = projectFiles.getIconPath(author, slug);
if (iconPath == null || !fileService.exists(iconPath)) {
public ResponseEntity<byte[]> getProjectIcon(final String author, final String slug) {
final String iconPath = this.projectFiles.getIconPath(author, slug);
if (iconPath == null || !this.fileService.exists(iconPath)) {
throw new InternalHangarException("Default to avatar url");
}
try {
HttpHeaders headers = new HttpHeaders();
headers.setCacheControl(CacheControl.maxAge(3600, TimeUnit.SECONDS).getHeaderValue());
return ResponseEntity.ok().headers(headers).body(fileService.bytes(iconPath));
} catch (IOException e) {
return ResponseEntity.ok().cacheControl(CacheControl.noCache()).body(this.fileService.bytes(iconPath));
} catch (final IOException e) {
e.printStackTrace();
throw new HangarApiException(HttpStatus.INTERNAL_SERVER_ERROR, "Unable to fetch project icon");
}
}
public String getUserIcon(String author) {
return String.format(config.security.api().avatarUrl(), author);
public String getUserIcon(final String author) {
return String.format(this.config.security.api().avatarUrl(), author);
}
}

View File

@ -4,15 +4,14 @@ import io.papermc.hangar.config.hangar.StorageConfig;
import io.papermc.hangar.model.common.Platform;
import io.papermc.hangar.service.internal.file.FileService;
import io.papermc.hangar.util.FileUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@Component
public class ProjectFiles {
@ -23,77 +22,77 @@ public class ProjectFiles {
private final FileService fileService;
@Autowired
public ProjectFiles(StorageConfig storageConfig, FileService fileService) {
public ProjectFiles(final StorageConfig storageConfig, final FileService fileService) {
this.fileService = fileService;
Path uploadsDir = Path.of(storageConfig.workDir());
pluginsDir = fileService.resolve(fileService.getRoot(), "plugins");
tmpDir = uploadsDir.resolve("tmp");
if (Files.exists(tmpDir)) {
FileUtils.deleteDirectory(tmpDir);
final Path uploadsDir = Path.of(storageConfig.workDir());
this.pluginsDir = fileService.resolve(fileService.getRoot(), "plugins");
this.tmpDir = uploadsDir.resolve("tmp");
if (Files.exists(this.tmpDir)) {
FileUtils.deleteDirectory(this.tmpDir);
}
logger.info("Cleaned up tmp files and inited work dir {} ", uploadsDir);
}
public String getProjectDir(String owner, String name) {
return fileService.resolve(getUserDir(owner), name);
public String getProjectDir(final String owner, final String slug) {
return this.fileService.resolve(this.getUserDir(owner), slug);
}
public String getVersionDir(String owner, String name, String version) {
return fileService.resolve(fileService.resolve(getProjectDir(owner, name), "versions"), version);
public String getVersionDir(final String owner, final String slug, final String version) {
return this.fileService.resolve(this.fileService.resolve(this.getProjectDir(owner, slug), "versions"), version);
}
public String getVersionDir(String owner, String name, String version, Platform platform) {
return fileService.resolve(getVersionDir(owner, name, version), platform.name());
public String getVersionDir(final String owner, final String slug, final String version, final Platform platform) {
return this.fileService.resolve(this.getVersionDir(owner, slug, version), platform.name());
}
public String getVersionDir(String owner, String name, String version, Platform platform, String fileName) {
return fileService.resolve(getVersionDir(owner, name, version, platform), fileName);
public String getVersionDir(final String owner, final String name, final String version, final Platform platform, final String fileName) {
return this.fileService.resolve(this.getVersionDir(owner, name, version, platform), fileName);
}
public String getUserDir(String user) {
return fileService.resolve(pluginsDir, user);
public String getUserDir(final String user) {
return this.fileService.resolve(this.pluginsDir, user);
}
public void transferProject(String owner, String newOwner, String slug) {
final String oldProjectDir = getProjectDir(owner, slug);
final String newProjectDir = getProjectDir(newOwner, slug);
public void transferProject(final String owner, final String newOwner, final String slug) {
final String oldProjectDir = this.getProjectDir(owner, slug);
final String newProjectDir = this.getProjectDir(newOwner, slug);
try {
fileService.move(oldProjectDir, newProjectDir);
} catch (IOException e) {
e.printStackTrace();
}
}
public void renameProject(String owner, String slug, String newSlug) {
final String oldProjectDir = getProjectDir(owner, slug);
final String newProjectDir = getProjectDir(owner, newSlug);
try {
fileService.move(oldProjectDir, newProjectDir);
this.fileService.move(oldProjectDir, newProjectDir);
} catch (final IOException e) {
e.printStackTrace();
}
}
public void renameVersion(String owner, String slug, String version, String newVersionName) {
final String oldVersionDir = getVersionDir(owner, slug, version);
final String newVersionDir = getVersionDir(owner, slug, newVersionName);
public void renameProject(final String owner, final String slug, final String newSlug) {
final String oldProjectDir = this.getProjectDir(owner, slug);
final String newProjectDir = this.getProjectDir(owner, newSlug);
try {
fileService.move(oldVersionDir, newVersionDir);
this.fileService.move(oldProjectDir, newProjectDir);
} catch (final IOException e) {
e.printStackTrace();
}
}
public String getIconsDir(String owner, String name) {
return fileService.resolve(getProjectDir(owner, name), "icons");
public void renameVersion(final String owner, final String slug, final String version, final String newVersionName) {
final String oldVersionDir = this.getVersionDir(owner, slug, version);
final String newVersionDir = this.getVersionDir(owner, slug, newVersionName);
try {
this.fileService.move(oldVersionDir, newVersionDir);
} catch (final IOException e) {
e.printStackTrace();
}
}
public String getIconPath(String owner, String name) {
return fileService.resolve(getIconsDir(owner, name), "icon.png");
public String getIconsDir(final String owner, final String slug) {
return this.fileService.resolve(this.getProjectDir(owner, slug), "icons");
}
public Path getTempDir(String owner) {
return tmpDir.resolve(owner);
public String getIconPath(final String owner, final String slug) {
return this.fileService.resolve(this.getIconsDir(owner, slug), "icon.png");
}
public Path getTempDir(final String owner) {
return this.tmpDir.resolve(owner);
}
}

View File

@ -9,7 +9,7 @@ import { useVuelidate } from "@vuelidate/core";
import { Cropper, type CropperResult } from "vue-advanced-cropper";
import { PaginatedResult, User } from "hangar-api";
import { useSeo } from "~/composables/useSeo";
import { projectIconUrl } from "~/composables/useUrlHelper";
import { avatarUrl, projectIconUrl } from "~/composables/useUrlHelper";
import Card from "~/lib/components/design/Card.vue";
import MemberList from "~/components/projects/MemberList.vue";
import { hasPerms } from "~/composables/usePerm";
@ -65,11 +65,13 @@ if (!form.settings.license.type) {
form.settings.license.type = "Unspecified";
}
const hasCustomIcon = ref(props.project.customIcon);
const projectIcon = ref<File | null>(null);
const cropperInput = ref();
const cropperResult = ref();
const imgSrc = ref(projectIconUrl(props.project.namespace.owner, props.project.namespace.slug));
let reader: FileReader | null = null;
onMounted(async () => {
onMounted(() => {
reader = new FileReader();
reader.addEventListener(
"load",
@ -78,7 +80,6 @@ onMounted(async () => {
},
false
);
await loadIconIntoCropper();
});
watch(projectIcon, (newValue) => {
@ -93,16 +94,6 @@ function changeImage({ canvas }: CropperResult) {
});
}
async function loadIconIntoCropper() {
const response = await fetch(projectIconUrl(props.project.namespace.owner, props.project.namespace.slug, false), {
headers: {
"Cache-Control": "no-cache",
},
});
const data = await response.blob();
reader?.readAsDataURL(data);
}
const newName = ref<string | null>("");
const newNameField = ref<InstanceType<typeof InputText> | null>(null);
const loading = reactive({
@ -210,8 +201,11 @@ async function uploadIcon() {
loading.uploadIcon = true;
try {
const response = await useInternalApi<string | null>(`projects/project/${route.params.user}/${route.params.project}/saveIcon`, "post", data);
imgSrc.value = URL.createObjectURL(cropperResult.value); // set temporary source so it changes right away
projectIcon.value = null;
cropperInput.value = null;
cropperResult.value = null;
await loadIconIntoCropper();
hasCustomIcon.value = true;
if (response) {
useNotificationStore().success(i18n.t("project.settings.success.changedIconWarn", [response]));
} else {
@ -232,8 +226,11 @@ async function resetIcon() {
} else {
useNotificationStore().success(i18n.t("project.settings.success.resetIcon"));
}
imgSrc.value = avatarUrl(props.project.owner.name); // set temporary source so it changes right away
projectIcon.value = null;
await loadIconIntoCropper();
cropperInput.value = null;
cropperResult.value = null;
hasCustomIcon.value = false;
} catch (e: any) {
handleRequestError(e);
}
@ -305,11 +302,11 @@ useHead(
<IconMdiUpload />
{{ i18n.t("project.settings.iconUpload") }}
</Button>
<Button :loading="loading.resetIcon" @click="resetIcon">
<IconMdiUpload />
<Button :disabled="!hasCustomIcon" :loading="loading.resetIcon" @click="resetIcon">
<IconMdiCached />
{{ i18n.t("project.settings.iconReset") }}
</Button>
<div class="col-span-1 col-start-3 row-start-1 row-span-3">
<div class="col-span-1 col-start-3 row-start-1 row-span-3" :class="{ 'justify-self-center': !cropperInput }">
<cropper
v-if="cropperInput"
:src="cropperInput"
@ -332,14 +329,7 @@ useHead(
image-restriction="stencil"
@change="changeImage"
/>
<img
v-else
id="project-icon-preview"
width="150"
height="150"
alt="Project Icon"
:src="projectIconUrl(project.namespace.owner, project.namespace.slug)"
/>
<img v-else id="project-icon-preview" width="150" height="150" alt="Project Icon" :src="imgSrc" />
</div>
</div>
</ProjectSettingsSection>

View File

@ -14,6 +14,7 @@ declare module "@vue/runtime-core" {
IconMdiAlertOutline: typeof import("~icons/mdi/alert-outline")["default"];
IconMdiBell: typeof import("~icons/mdi/bell")["default"];
IconMdiBellOutline: typeof import("~icons/mdi/bell-outline")["default"];
IconMdiCached: typeof import("~icons/mdi/cached")["default"];
IconMdiCalendar: typeof import("~icons/mdi/calendar")["default"];
IconMdiCancel: typeof import("~icons/mdi/cancel")["default"];
IconMdiCashMultiple: typeof import("~icons/mdi/cash-multiple")["default"];

View File

@ -44,6 +44,7 @@ declare module "hangar-internal" {
pages: HangarProjectPage[];
pinnedVersions: PinnedVersion[];
mainChannelVersions: Record<Platform, HangarVersion>;
customIcon: boolean;
}
interface ProjectPage extends Table {