switched versions table to vue

This commit is contained in:
Jake Potrebic 2020-09-16 12:53:38 -07:00
parent bb1a5b0fb9
commit 5bf2d594c7
No known key found for this signature in database
GPG Key ID: 7C58557EC9C421F8
8 changed files with 236 additions and 18 deletions

View File

@ -14,10 +14,12 @@
"@fortawesome/free-brands-svg-icons": "5.14.0",
"@fortawesome/free-regular-svg-icons": "5.14.0",
"@fortawesome/free-solid-svg-icons": "5.14.0",
"axios": "^0.20.0",
"jquery": "3.5.1",
"lodash": "4.17.20",
"query-string": "6.13.1",
"vue": "2.6.11"
"vue": "2.6.11",
"vuetable-2": "^2.0.0-beta.4"
},
"devDependencies": {
"@babel/core": "7.11.1",

View File

@ -0,0 +1,141 @@
<template>
<div>
<vuetable ref="platform-table" :api-mode="false" :data="data" :fields="fields" class="platform-versions">
<div slot="versions-slot" slot-scope="props">
<div class="platform-version" v-for="v in props.rowData.versions">
<span @click="removeVersion(props.rowData.platform, v)" style="cursor:pointer;">
<i class="fas fa-times" style="color: #bb0400"></i>
</span>
{{ v }}
</div>
</div>
<div slot="actions-slot" slot-scope="props">
<div class="form-group form-inline">
<label for="add-version-input" class="sr-only">Add Version</label>
<div class="input-group">
<div class="input-group-addon">Version Identifier</div>
<input type="text" id="add-version-input" class="form-control" v-model="inputs[props.rowData.platform]">
</div>
</div>
<button type="button" class="btn btn-primary" @click="addVersion(props.rowData.platform)" :disabled="!inputs[props.rowData.platform]">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</button>
</div>
</vuetable>
<button v-if="!loading" type="button" class="btn btn-success" @click="save" :disabled="!changesMade">
<span class="glyphicon glyphicon-floppy-disk"></span>
Save Changes
</button>
<div v-else>
<i class="fas fa-spinner fa-spin"></i>
<span>Loading projects for you...</span>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import axios from 'axios'
import Vuetable from 'vuetable-2'
export default {
name: 'PlatformVersionTable',
components: {
Vuetable
},
props: {
platforms: {
type: Object,
default: () => {}
}
},
data() {
return {
loading: false,
changesMade: false,
data: [],
inputs: {},
fields: [
{
name: 'platform',
width: '40px',
formatter(value) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
},
{
name: 'versions-slot',
title: '<i class="fas fa-tags"></i> Versions'
},
{
name: 'actions-slot',
title: '<i class="fas fa-plus"></i> Add Version'
}
]
}
},
methods: {
addVersion(platform) {
const versions = this.data.find(o => o.platform === platform).versions
if (versions.indexOf(this.inputs[platform]) < 0) {
versions.push(this.inputs[platform])
this.changesMade = true
}
this.inputs[platform] = ''
},
removeVersion(platform, version) {
const versions = this.data.find(o => o.platform === platform).versions
this.$delete(versions, versions.indexOf(version))
this.changesMade = true
},
save() {
const additions = {}
const removals = {}
for (const platform in this.platforms) {
const versions = this.data.find(o => o.platform === platform.toLowerCase()).versions
additions[platform] = _.difference(versions, this.platforms[platform])
removals[platform] = _.difference(this.platforms[platform], versions)
}
let hasChanges = false
for (const platform in this.platforms) {
if (additions[platform].length > 0 || removals[platform].length > 0) {
hasChanges = true
break
}
}
if (!hasChanges) { // NO actual changes
return
}
this.loading = true
axios.post("/admin/versions/", {
additions,
removals
}).then(() => {
this.changesMade = false
}).finally(() => {
this.loading = false
})
}
},
created() {
for (const platform in this.platforms) {
this.inputs[platform] = ''
this.data.push({
platform: platform.toLowerCase(),
versions: [...this.platforms[platform]]
})
}
}
}
</script>
<style lang="scss">
.platform-versions {
display: inherit;
}
.platform-version {
display: inline-block;
padding: 1px 4px 0 4px;
margin-right: 5px;
background-color: #cdcdcd;
border-radius: 2px;
}
</style>

View File

@ -0,0 +1,15 @@
import Vue from 'vue'
import axios from 'axios'
import PlatformVersionTable from '../PlatformVersionTable.vue'
axios.defaults.headers.post[window.csrfInfo.headerName] = window.csrfInfo.token;
const app = new Vue({
el: '#platform-version-table',
render: createElement => createElement(PlatformVersionTable, {
props: {
platforms: PLATFORMS
}
})
});

View File

@ -5,19 +5,22 @@ const CopyPlugin = require('copy-webpack-plugin');
//const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const Path = require('path');
const fs = require('fs');
const sourceDir = Path.resolve(__dirname, 'src');
const entryDir = Path.resolve(sourceDir, 'entries');
const modulesDir = Path.resolve(__dirname, 'node_modules');
const outputDir = Path.resolve(__dirname, '..', '..', '..', 'target', 'classes', 'public', 'build');
const javascriptDir = Path.resolve(__dirname, '..', '..', '..', 'target', 'classes', 'public', 'javascripts');
const entries = {}
for (const file of fs.readdirSync(entryDir)) {
entries[file.replace('.js', "")] = Path.resolve(entryDir, file);
}
module.exports = {
entry: {
main: Path.resolve(sourceDir, 'scss', 'main.scss'),
home: Path.resolve(entryDir, 'home.js'),
'font-awesome': Path.resolve(entryDir, 'font-awesome.js'),
'user-profile': Path.resolve(entryDir, 'user-profile.js'),
'version-list': Path.resolve(entryDir, 'version-list.js'),
...entries
},
output: {
path: outputDir,

View File

@ -1397,6 +1397,20 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
axios@^0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.15.3.tgz#2c9d638b2e191a08ea1d6cc988eadd6ba5bdc053"
integrity sha1-LJ1jiy4ZGgjqHWzJiOrda6W9wFM=
dependencies:
follow-redirects "1.0.0"
axios@^0.20.0:
version "0.20.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd"
integrity sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==
dependencies:
follow-redirects "^1.10.0"
babel-loader@8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3"
@ -2915,6 +2929,18 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.0.0.tgz#8e34298cbd2e176f254effec75a1c78cc849fd37"
integrity sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=
dependencies:
debug "^2.2.0"
follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -6590,6 +6616,13 @@ vue@2.6.11:
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
vuetable-2@^2.0.0-beta.4:
version "2.0.0-beta.4"
resolved "https://registry.yarnpkg.com/vuetable-2/-/vuetable-2-2.0.0-beta.4.tgz#474a28dfbf9372e88b34b0659f4b524bc19aa676"
integrity sha512-sWSdKIPYiATI9hOuPlQ09iSWEFAfhe6sPovl4hK2SemvqvgxLIF8UjG3LwJJC7idWS9UPpK9fRrSXoFJhRcluQ==
dependencies:
axios "^0.15.3"
watchpack-chokidar2@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0"

View File

@ -1,5 +1,7 @@
package io.papermc.hangar.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.papermc.hangar.controller.util.StatusZ;
import io.papermc.hangar.db.customtypes.LoggedActionType;
@ -30,7 +32,6 @@ import io.papermc.hangar.service.VersionService;
import io.papermc.hangar.service.project.FlagService;
import io.papermc.hangar.service.project.ProjectService;
import io.papermc.hangar.util.AlertUtil;
import io.papermc.hangar.util.Routes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.format.annotation.DateTimeFormat;
@ -43,6 +44,7 @@ import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@ -72,11 +74,12 @@ public class ApplicationController extends HangarController {
private final SitemapService sitemapService;
private final StatsService statsService;
private final StatusZ statusZ;
private final ObjectMapper mapper;
private final HttpServletRequest request;
@Autowired
public ApplicationController(HangarDao<PlatformVersionsDao> platformVersionsDao, UserService userService, ProjectService projectService, OrgService orgService, VersionService versionService, FlagService flagService, UserActionLogService userActionLogService, JobService jobService, SitemapService sitemapService, StatsService statsService, StatusZ statusZ, HttpServletRequest request) {
public ApplicationController(HangarDao<PlatformVersionsDao> platformVersionsDao, UserService userService, ProjectService projectService, OrgService orgService, VersionService versionService, FlagService flagService, UserActionLogService userActionLogService, JobService jobService, SitemapService sitemapService, StatsService statsService, StatusZ statusZ, ObjectMapper mapper, HttpServletRequest request) {
this.platformVersionsDao = platformVersionsDao;
this.userService = userService;
this.projectService = projectService;
@ -87,6 +90,7 @@ public class ApplicationController extends HangarController {
this.jobService = jobService;
this.sitemapService = sitemapService;
this.statusZ = statusZ;
this.mapper = mapper;
this.request = request;
this.statsService = statsService;
}
@ -245,21 +249,31 @@ public class ApplicationController extends HangarController {
for (Platform p : Platform.getValues()) {
versions.putIfAbsent(p, new ArrayList<>());
}
mav.addObject("platformVersions", versions);
mav.addObject("platformVersions", mapper.valueToTree(versions));
return fillModel(mav);
}
// @SuppressWarnings("unchecked")
@GlobalPermission(NamedPermission.MANUAL_VALUE_CHANGES)
@Secured("ROLE_USER")
@PostMapping("/admin/versions/{platform}")
public ModelAndView updatePlatformVersions(@PathVariable Platform platform, @RequestParam(required = false) List<String> versions, @RequestParam(required = false) List<String> removedVersions) {
if (versions != null) {
platformVersionsDao.get().insert(versions.stream().map(s -> new PlatformVersionsTable(platform, s)).collect(Collectors.toList()));
@PostMapping("/admin/versions/")
@ResponseStatus(HttpStatus.OK)
public void updatePlatformVersions(@RequestBody ObjectNode object) {
Map<String, List<String>> additions;
Map<String, List<String>> removals;
try {
additions = mapper.treeToValue(object.get("additions"), (Class<Map<String,List<String>>>)(Class)Map.class);
removals = mapper.treeToValue(object.get("removals"), (Class<Map<String,List<String>>>)(Class)Map.class);
} catch (JsonProcessingException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad formatting", e);
}
if (removedVersions != null) {
platformVersionsDao.get().delete(removedVersions, platform.ordinal());
if (additions == null || removals == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad formatting");
}
return Routes.SHOW_PLATFORM_VERSIONS.getRedirect();
additions.forEach((platform, versions) -> {
platformVersionsDao.get().insert(versions.stream().map(v -> new PlatformVersionsTable(Platform.valueOf(platform), v)).collect(Collectors.toList()));
});
removals.forEach((platform, versions) -> platformVersionsDao.get().delete(versions, Platform.valueOf(platform).ordinal()));
}
@GlobalPermission(NamedPermission.EDIT_ALL_USER_SETTINGS)

View File

@ -98,6 +98,10 @@ showFooter: Boolean = true, noContainer: Boolean = false, additionalMeta: Html =
<#if _csrf?? && _csrf.token??>
<script>
window.csrf = '${_csrf.token}';
window.csrfInfo = {
'headerName': '${_csrf.headerName}',
'token': '${_csrf.token}'
};
window.ajaxSettings = {"headers": { '${_csrf.headerName}': window.csrf}};
window.isLoggedIn = ${headerData.hasUser()?c};
$.ajaxSetup(window.ajaxSettings);

View File

@ -5,14 +5,20 @@
<#import "*/layout/base.ftlh" as base />
<#assign scriptsVar>
<link rel="stylesheet" href="<@hangar.url "build/platform-version-table.css" />">
<script type="text/javascript" src="<@hangar.url "javascripts/platformVersions.js" />"></script>
<script type="text/javascript" src="<@hangar.url "CSSescape/css.escape.js" />"></script>
<script>
PLATFORMS = ${platformVersions}
</script>
<script type="text/javascript" src="<@hangar.url "build/platform-version-table.js" />"></script>
</#assign>
<#assign message><@spring.message "admin.platformVersions.title" /></#assign>
<@base.base title="${message}" additionalScripts=scriptsVar>
<#assign Platform=@helper["io.papermc.hangar.model.Platform"] />
<#-- @ftlvariable name="Platform" type="io.papermc.hangar.model.Platform" -->
<div id="platform-version-table"></div>
<#--<#assign Platform=@helper["io.papermc.hangar.model.Platform"] />
&lt;#&ndash; @ftlvariable name="Platform" type="io.papermc.hangar.model.Platform" &ndash;&gt;
<#list platformVersions as platform, versions>
<@form.form action="${Routes.UPDATE_PLATFORM_VERSIONS.getRouteUrl(platform.ordinal()?string)}" method="POST" id="${platform.getName()?lower_case}-form" class="form-inline">
<@csrf.formField />
@ -49,5 +55,5 @@
</button>
<div style="width: 80%; border-top: #555555 solid 1px; margin-top: 10px;"></div>
</@form.form>
</#list>
</#list>-->
</@base.base>