mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-02 02:51:40 +00:00
Update log viewer, add uncalibrated modal (#108)
Adds a prettier log viewer, with the ability to filter logs. Also warns user and prevents switching into 3d mode if the resolution is uncalibrated.
This commit is contained in:
@@ -40,6 +40,7 @@
|
||||
<v-list-item
|
||||
link
|
||||
to="cameras"
|
||||
ref="camerasTabOpener"
|
||||
@click="switchToDriverMode()"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
@@ -71,7 +72,6 @@
|
||||
<v-list-item-title>Documentation</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-if="this.$vuetify.breakpoint.mdAndUp"
|
||||
link
|
||||
@@ -111,63 +111,43 @@
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
<v-content>
|
||||
<v-main>
|
||||
<v-container
|
||||
fluid
|
||||
fill-height
|
||||
>
|
||||
<v-layout>
|
||||
<v-flex>
|
||||
<router-view @save="startTimer" />
|
||||
<v-snackbar
|
||||
v-model="saveSnackbar"
|
||||
:timeout="1000"
|
||||
top
|
||||
color="accent"
|
||||
>
|
||||
<div style="text-align: center;width: 100%;">
|
||||
<h4>Saved All changes</h4>
|
||||
</div>
|
||||
</v-snackbar>
|
||||
<div v-if="isLogger">
|
||||
<keep-alive>
|
||||
<log-view
|
||||
class="loggerClass"
|
||||
:log="log"
|
||||
/>
|
||||
</keep-alive>
|
||||
</div>
|
||||
<router-view v-on:switch-to-cameras="switchToDriverMode" />
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-content>
|
||||
</v-main>
|
||||
|
||||
<v-dialog
|
||||
v-model="$store.state.logsOverlay"
|
||||
width="1500"
|
||||
dark
|
||||
>
|
||||
<logs />
|
||||
</v-dialog>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import logView from '@femessage/log-viewer';
|
||||
import Logs from "./views/LogsView"
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
logView
|
||||
Logs
|
||||
},
|
||||
data: () => ({
|
||||
// Used so that we can switch back to the previously selected pipeline after camera calibration
|
||||
previouslySelectedIndex: undefined,
|
||||
timer: undefined,
|
||||
isLogger: false,
|
||||
log: "",
|
||||
}),
|
||||
computed: {
|
||||
saveSnackbar: {
|
||||
get() {
|
||||
return this.$store.state.saveBar;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit("saveBar", value);
|
||||
}
|
||||
},
|
||||
compact: {
|
||||
get() {
|
||||
if (this.$store.state.compactMode === undefined) {
|
||||
@@ -186,8 +166,8 @@
|
||||
created() {
|
||||
document.addEventListener("keydown", e => {
|
||||
switch (e.key) {
|
||||
case '`' :
|
||||
this.isLogger = !this.isLogger;
|
||||
case "`":
|
||||
this.$store.state.logsOverlay = !this.$store.state.logsOverlay;
|
||||
break;
|
||||
case "z":
|
||||
if (e.ctrlKey && this.$store.getters.canUndo) {
|
||||
@@ -250,22 +230,12 @@
|
||||
toggleCompactMode() {
|
||||
this.compact = !this.compact;
|
||||
},
|
||||
saveSettings() {
|
||||
clearInterval(this.timer);
|
||||
this.saveSnackbar = true;
|
||||
this.handleInput("command", "save");
|
||||
},
|
||||
startTimer() {
|
||||
if (this.timer !== undefined) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
this.timer = setInterval(this.saveSettings, 4000);
|
||||
},
|
||||
logMessage(message, level) {
|
||||
const colors = ["\u001B[30m", "\u001B[31m", "\u001B[33m", "\u001B[32m", "\u001B[37m", "\u001B[36m"]
|
||||
const reset = "\u001b[0m"
|
||||
this.log += `${colors[level]}${message}${reset}\n`
|
||||
console.log(message)
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
logMessage(message, levelInt) {
|
||||
this.$store.commit('logString', {
|
||||
['level']: levelInt,
|
||||
['message']: message
|
||||
})
|
||||
},
|
||||
switchToDriverMode() {
|
||||
this.previouslySelectedIndex = this.$store.getters.currentPipelineIndex;
|
||||
@@ -307,16 +277,6 @@
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.loggerClass {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 25% !important;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-shadow: #282828 0 0 5px 1px;
|
||||
background-color: #2b2b2b;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0.5em;
|
||||
border-radius: 5px;
|
||||
@@ -341,21 +301,23 @@
|
||||
#title {
|
||||
color: #ffd843;
|
||||
}
|
||||
|
||||
span {
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* Hack */
|
||||
/* Hacks */
|
||||
|
||||
.v-divider {
|
||||
border-color: white !important;
|
||||
}
|
||||
|
||||
.v-input {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
.v-input {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
/* This is unfortunately the only way to override table background color */
|
||||
.theme--dark.v-data-table > .v-data-table__wrapper > table > tbody > tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
|
||||
background: #005281 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
height: `${this.scale}%`,
|
||||
cursor: (this.colorPicking ? `url(${require("../../assets/eyedropper.svg")}),` : "") + "default",
|
||||
};
|
||||
console.log(ret);
|
||||
|
||||
if (this.$vuetify.breakpoint.xl) {
|
||||
ret["max-height"] = this.maxHeightXl;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<span
|
||||
style="cursor: text !important;"
|
||||
class="white--text"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>{{ text }}</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
align="center"
|
||||
cols="12"
|
||||
>
|
||||
<span class="text--white">Target Location</span>
|
||||
<span class="white--text">Target Location</span>
|
||||
<canvas
|
||||
id="canvasId"
|
||||
class="mt-2"
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
<v-menu
|
||||
offset-y
|
||||
auto
|
||||
v-if="!$store.getters.isDriverMode"
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-icon
|
||||
|
||||
@@ -4,6 +4,7 @@ import Dashboard from "./views/PipelineView";
|
||||
import Cameras from "./views/CamerasView";
|
||||
import Settings from "./views/SettingsView";
|
||||
import Docs from "./views/DocsView";
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
export default new Router({
|
||||
|
||||
@@ -11,18 +11,14 @@ const set = key => (state, val) => {
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
reflectivePipelineSettings: {
|
||||
state: {
|
||||
currentResolutionIndex: 0,
|
||||
},
|
||||
},
|
||||
undoRedo: undoRedo
|
||||
},
|
||||
state: {
|
||||
backendConnected: false,
|
||||
colorPicking: false,
|
||||
saveBar: false,
|
||||
logsOverlay: false,
|
||||
compactMode: localStorage.getItem("compactMode") === undefined ? undefined : localStorage.getItem("compactMode") === "true", // Compact mode is initially unset on purpose
|
||||
logMessages: [],
|
||||
currentCameraIndex: 0,
|
||||
selectedOutputs: [0, 1], // 0 indicates normal, 1 indicates threshold
|
||||
cameraSettings: [ // This is a list of objects representing the settings of all cameras
|
||||
@@ -132,13 +128,17 @@ export default new Vuex.Store({
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
saveBar: set('saveBar'),
|
||||
compactMode: set('compactMode'),
|
||||
cameraSettings: set('cameraSettings'),
|
||||
currentCameraIndex: set('currentCameraIndex'),
|
||||
selectedOutputs: set('selectedOutputs'),
|
||||
settings: set('settings'),
|
||||
calibrationData: set('calibrationData'),
|
||||
logString: (state, newStr) => {
|
||||
const str = state.logMessages;
|
||||
str.push(newStr)
|
||||
Vue.set(state, 'logString', str)
|
||||
},
|
||||
|
||||
solvePNPEnabled: (state, val) => {
|
||||
state.cameraSettings[state.currentCameraIndex].currentPipelineSettings.solvePNPEnabled = val;
|
||||
@@ -205,6 +205,11 @@ export default new Vuex.Store({
|
||||
currentPipelineResults: state => {
|
||||
return state.pipelineResults;
|
||||
},
|
||||
isCalibrated: state => {
|
||||
let resolution = state.cameraSettings[state.currentCameraIndex].videoFormatList[state.cameraSettings[state.currentCameraIndex].currentPipelineSettings.cameraVideoModeIndex];
|
||||
return state.cameraSettings[state.currentCameraIndex].calibrations
|
||||
.some(e => e.width === resolution.width && e.height === resolution.height);
|
||||
},
|
||||
cameraList: state => state.cameraSettings.map(it => it.nickname),
|
||||
currentCameraSettings: state => state.cameraSettings[state.currentCameraIndex],
|
||||
currentCameraIndex: state => state.currentCameraIndex,
|
||||
|
||||
@@ -232,7 +232,7 @@
|
||||
<v-icon left>
|
||||
mdi-download
|
||||
</v-icon>
|
||||
Download Checkerboard
|
||||
Download Chessboard
|
||||
</v-btn>
|
||||
<a
|
||||
ref="calibrationFile"
|
||||
@@ -415,8 +415,8 @@ export default {
|
||||
return this.filteredVideomodeIndex
|
||||
},
|
||||
set(i) {
|
||||
console.log(`Setting filtered index to ${i}`)
|
||||
this.filteredVideomodeIndex = i
|
||||
console.log(`Setting filtered index to ${i}`);
|
||||
this.filteredVideomodeIndex = i;
|
||||
this.$store.commit('mutateCalibrationState', {['videoModeIndex']: this.filteredResolutionList[i].index});
|
||||
}
|
||||
},
|
||||
@@ -430,13 +430,13 @@ export default {
|
||||
if(cal.width === resolution.width && cal.height === resolution.height) {
|
||||
ret = cal
|
||||
}
|
||||
})
|
||||
});
|
||||
return ret;
|
||||
},
|
||||
downloadBoard() {
|
||||
this.axios.get("http://" + this.$address + require('../assets/chessboard.png'), {responseType: 'blob'}).then((response) => {
|
||||
require('downloadjs')(response.data, "Calibration Board", "image/png")
|
||||
})
|
||||
require('downloadjs')(response.data, "Calibration Board", "image/png");
|
||||
});
|
||||
},
|
||||
sendCameraSettings() {
|
||||
this.axios.post("http://" + this.$address + "/api/settings/camera", {
|
||||
@@ -453,7 +453,7 @@ export default {
|
||||
|
||||
isCalibrated(resolution) {
|
||||
return this.$store.getters.currentCameraSettings.calibrations
|
||||
.some(e => e.width === resolution.width && e.height === resolution.height)
|
||||
.some(e => e.width === resolution.width && e.height === resolution.height);
|
||||
},
|
||||
|
||||
sendCalibrationMode() {
|
||||
@@ -464,20 +464,20 @@ export default {
|
||||
if (this.isCalibrating === true) {
|
||||
data['takeCalibrationSnapshot'] = true
|
||||
} else {
|
||||
const calData = this.calibrationData
|
||||
calData.isCalibrating = true
|
||||
data['startPnpCalibration'] = calData
|
||||
const calData = this.calibrationData;
|
||||
calData.isCalibrating = true;
|
||||
data['startPnpCalibration'] = calData;
|
||||
|
||||
console.log("starting calibration with index " + calData.videoModeIndex)
|
||||
console.log("starting calibration with index " + calData.videoModeIndex);
|
||||
}
|
||||
|
||||
this.$socket.send(this.$msgPack.encode(data));
|
||||
},
|
||||
sendCalibrationFinish() {
|
||||
console.log("finishing calibration for index " + this.$store.getters.currentCameraIndex)
|
||||
console.log("finishing calibration for index " + this.$store.getters.currentCameraIndex);
|
||||
|
||||
this.snackbar.text = "Calibrating...";
|
||||
this.snackbar.color = "secondary"
|
||||
this.snackbar.color = "secondary";
|
||||
this.snack = true;
|
||||
|
||||
this.axios.post("http://" + this.$address + "/api/settings/endCalibration", this.$store.getters.currentCameraIndex)
|
||||
@@ -525,9 +525,4 @@ export default {
|
||||
.v-data-table th,td {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
/** This is unfortunately the only way to override table background color **/
|
||||
.theme--dark.v-data-table tbody tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
|
||||
background: #005281;
|
||||
}
|
||||
</style>
|
||||
130
photon-client/src/views/LogsView.vue
Normal file
130
photon-client/src/views/LogsView.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<v-card
|
||||
dark
|
||||
class="pt-3"
|
||||
color="primary"
|
||||
flat
|
||||
>
|
||||
<v-card-title>
|
||||
View Program Logs
|
||||
|
||||
<v-btn
|
||||
color="secondary"
|
||||
style="margin-left: auto;"
|
||||
depressed
|
||||
@click="download('photonlog.log', rawLogs.map(it => it.message).join('\n'))"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-download
|
||||
</v-icon>
|
||||
Download Log
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<div class="pr-6 pl-6">
|
||||
<v-btn-toggle
|
||||
v-model="logLevel"
|
||||
dark
|
||||
multiple
|
||||
class="fill mb-4"
|
||||
>
|
||||
<v-btn
|
||||
v-for="(level) in possibleLevelArray"
|
||||
:key="level"
|
||||
color="secondary"
|
||||
class="fill"
|
||||
>
|
||||
{{ level }}
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
<!-- Logs -->
|
||||
|
||||
<v-virtual-scroll
|
||||
:items="logMessageArray"
|
||||
item-height="50"
|
||||
height="600"
|
||||
>
|
||||
<template v-slot="{ item }">
|
||||
<div :class="[getColor(item) + '--text', 'log-item']">{{ item.message }}</div>
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</div>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="white"
|
||||
text
|
||||
@click="$store.state.logsOverlay = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "Logs",
|
||||
components: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedLevel: [0, 1, 2],
|
||||
possibleLevelArray: ['ERROR', 'WARN', 'INFO', 'DEBUG'],
|
||||
colorArray: ['red', 'yellow','green', 'white'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rawLogs() {
|
||||
return this.$store.state.logMessages;
|
||||
},
|
||||
logMessageArray() {
|
||||
const logArray = this.$store.state.logMessages;
|
||||
return logArray.filter(it => this.selectedLevel.includes(it.level));
|
||||
},
|
||||
logLevel: {
|
||||
get() {
|
||||
return this.selectedLevel
|
||||
},
|
||||
set(value) {
|
||||
this.selectedLevel = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getColor(message) {
|
||||
return this.colorArray[message.level];
|
||||
},
|
||||
|
||||
download(filename, text) {
|
||||
const element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||
element.setAttribute('download', filename);
|
||||
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-btn-toggle.fill {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v-btn-toggle.fill > .v-btn {
|
||||
width: 25%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -82,6 +82,7 @@
|
||||
align="center"
|
||||
class="pl-3 pr-3"
|
||||
>
|
||||
<!-- -->
|
||||
<v-col lg="12">
|
||||
<p style="color: white;">
|
||||
Processing mode:
|
||||
@@ -100,6 +101,7 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="on3DClick"
|
||||
>
|
||||
<v-icon>mdi-cube-outline</v-icon>
|
||||
<span>3D</span>
|
||||
@@ -180,7 +182,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<!-- snack bar -->
|
||||
<!-- snack bar and modal -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:timeout="3000"
|
||||
@@ -196,6 +198,44 @@
|
||||
Close
|
||||
</v-btn>
|
||||
</v-snackbar>
|
||||
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
width="500"
|
||||
>
|
||||
<v-card
|
||||
color="primary"
|
||||
dark
|
||||
>
|
||||
<v-card-title>
|
||||
Current resolution not calibrated
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
Because the current resolution {{ this.$store.getters.videoFormatList[this.$store.getters.currentPipelineSettings.cameraVideoModeIndex].width }}
|
||||
x {{ this.$store.getters.videoFormatList[this.$store.getters.currentPipelineSettings.cameraVideoModeIndex].height }}
|
||||
is not yet calibrated, 3D mode cannot be enabled. Please
|
||||
<a
|
||||
href="/#/cameras"
|
||||
class="white--text"
|
||||
@click="$emit('switch-to-cameras')"
|
||||
> visit the Cameras tab</a> to calibrate this resolution. For now, SolvePNP will do nothing.
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="white"
|
||||
text
|
||||
@click="closeUncalibratedDialog"
|
||||
>
|
||||
OK
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -226,6 +266,8 @@
|
||||
selectedTabsData: [0, 0, 0, 0],
|
||||
snackbar: false,
|
||||
counterData: 0,
|
||||
dialog: false,
|
||||
processingModeOverride: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -293,11 +335,13 @@
|
||||
},
|
||||
processingMode: {
|
||||
get() {
|
||||
return this.$store.getters.currentPipelineSettings.solvePNPEnabled ? 1 : 0;
|
||||
return (this.$store.getters.currentPipelineSettings.solvePNPEnabled || this.processingModeOverride) ? 1 : 0;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.getters.currentPipelineSettings.solvePNPEnabled = value === 1;
|
||||
this.handlePipelineUpdate("solvePNPEnabled", value === 1);
|
||||
if (this.$store.getters.isCalibrated) {
|
||||
this.$store.getters.currentPipelineSettings.solvePNPEnabled = value === 1;
|
||||
this.handlePipelineUpdate("solvePNPEnabled", value === 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
driverMode: {
|
||||
@@ -350,6 +394,11 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isCalibrated() {
|
||||
const resolution = this.$store.getters.videoFormatList[this.$store.getters.currentPipelineSettings.cameraVideoModeIndex];
|
||||
return this.$store.getters.currentCameraSettings.calibrations
|
||||
.some(e => e.width === resolution.width && e.height === resolution.height)
|
||||
},
|
||||
onImageClick(event) {
|
||||
// Only run on the input stream
|
||||
if (event.target.alt !== "Stream0") return;
|
||||
@@ -358,6 +407,18 @@
|
||||
if (ref && ref[0])
|
||||
ref[0].onClick(event)
|
||||
},
|
||||
on3DClick() {
|
||||
if (!this.$store.getters.isCalibrated) {
|
||||
this.dialog = true;
|
||||
this.processingModeOverride = true;
|
||||
}
|
||||
},
|
||||
closeUncalibratedDialog() {
|
||||
this.dialog = false;
|
||||
this.processingModeOverride = false;
|
||||
// this.$store.getters.currentPipelineSettings.solvePNPEnabled = false;
|
||||
this.handlePipelineUpdate("solvePNPEnabled", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<span>Target Manipulation</span>
|
||||
<span class="white--text">Target Manipulation</span>
|
||||
<v-divider class="mt-2" />
|
||||
|
||||
<CVselect
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
@rollback="e=> rollback('outputShowMultipleTargets', e)"
|
||||
/>
|
||||
<span>Robot Offset</span>
|
||||
<span class="white--text">Robot Offset</span>
|
||||
<v-divider class="mt-2" />
|
||||
<CVselect
|
||||
v-model="offsetRobotOffsetMode"
|
||||
|
||||
@@ -96,11 +96,6 @@
|
||||
return Math.atan(Math.tan(diagonalView / 2) * (resolution.width / diagonalAspect)) * 2 * (180 / Math.PI)
|
||||
}
|
||||
},
|
||||
allow3D: {
|
||||
get() {
|
||||
return this.$store.getters.currentCameraSettings.calibrated;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let tmp = [];
|
||||
|
||||
@@ -97,9 +97,4 @@
|
||||
.v-data-table td {
|
||||
font-family: monospace !important;
|
||||
}
|
||||
|
||||
/** This is unfortunately the only way to override table background color **/
|
||||
.theme--dark.v-data-table tbody tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
|
||||
background: #005281;
|
||||
}
|
||||
</style>
|
||||
@@ -67,6 +67,7 @@
|
||||
v-model="snack"
|
||||
top
|
||||
:color="snackbar.color"
|
||||
timeout="0"
|
||||
>
|
||||
<span>{{ snackbar.text }}</span>
|
||||
</v-snackbar>
|
||||
@@ -115,15 +116,16 @@ export default {
|
||||
{headers: {"Content-Type": "multipart/form-data"}}).then(() => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: "Settings imported successfully",
|
||||
text: "Settings imported successfully! Program will now exit...",
|
||||
};
|
||||
this.snack = true;
|
||||
}).catch(() => {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Couldn't import settings",
|
||||
}
|
||||
color: "success",
|
||||
text: "Settings imported successfully! Program will now exit...",
|
||||
};
|
||||
this.snack = true;
|
||||
});
|
||||
this.snack = true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user