stash
This commit is contained in:
parent
2218cc3543
commit
4671860fa4
6 changed files with 166 additions and 2 deletions
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.2.2
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('toolshed', '0023_remove_storagelocation_storagelocation_unique_owner_scoped_id_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='workflowinstance',
|
||||||
|
name='current_step',
|
||||||
|
field=models.PositiveIntegerField(default=1),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -236,6 +236,7 @@ class StorageLocation(models.Model):
|
||||||
class WorkflowInstance(models.Model):
|
class WorkflowInstance(models.Model):
|
||||||
slug = models.CharField(max_length=255)
|
slug = models.CharField(max_length=255)
|
||||||
state = models.CharField(max_length=255)
|
state = models.CharField(max_length=255)
|
||||||
|
current_step = models.PositiveIntegerField(default=1)
|
||||||
payload = models.TextField(default='', blank=True) # an opaque, frontend-serialized JSON string on the backend.
|
payload = models.TextField(default='', blank=True) # an opaque, frontend-serialized JSON string on the backend.
|
||||||
owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='workflows')
|
owner = models.ForeignKey(ToolshedUser, on_delete=models.CASCADE, related_name='workflows')
|
||||||
staged_files = models.ManyToManyField(File, related_name='staged_by_workflows', blank=True)
|
staged_files = models.ManyToManyField(File, related_name='staged_by_workflows', blank=True)
|
||||||
|
|
|
||||||
|
|
@ -319,7 +319,7 @@ class WorkflowInstanceSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WorkflowInstance
|
model = WorkflowInstance
|
||||||
fields = ['id', 'slug', 'state', 'payload', 'owner', 'staged_files', 'created_at', 'updated_at']
|
fields = ['id', 'slug', 'state', 'current_step', 'payload', 'owner', 'staged_files', 'created_at', 'updated_at']
|
||||||
read_only_fields = ['owner', 'staged_files', 'created_at', 'updated_at']
|
read_only_fields = ['owner', 'staged_files', 'created_at', 'updated_at']
|
||||||
|
|
||||||
def get_staged_files(self, obj):
|
def get_staged_files(self, obj):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div v-if="loading" class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
|
<template v-else-if="item">
|
||||||
|
<authenticated-image v-if="thumbnailFile" :src="thumbnailPathForHash(thumbnailFile.hash)"
|
||||||
|
:owner="thumbnailFile.owner" img-class="completed-item-thumb me-2"/>
|
||||||
|
<b-icon-image v-else class="me-2"></b-icon-image>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="fw-bold small">{{ item.name }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="text-muted small">
|
||||||
|
<b-icon-exclamation-triangle class="me-1"></b-icon-exclamation-triangle>
|
||||||
|
Item #{{ id }} (unavailable)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as BIcons from "bootstrap-icons-vue";
|
||||||
|
import {mapActions} from "vuex";
|
||||||
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FotoFirstCompletedItemCard',
|
||||||
|
components: {AuthenticatedImage, ...BIcons},
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: [Number, String],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
ownerGroup: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
item: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
thumbnailFile() {
|
||||||
|
return this.item?.files?.find(file => file.mime_type?.startsWith('image/')) || null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
// Always live: this card exists specifically so the summary reflects the item's current
|
||||||
|
// name/photo, not whatever it looked like at the moment this workflow created it.
|
||||||
|
try {
|
||||||
|
this.item = await this.fetchItemByHandle({handle: this.ownerGroup || this.owner, id: this.id});
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['fetchItemByHandle']),
|
||||||
|
// Requests a small server-resized/cached thumbnail instead of the original upload - this
|
||||||
|
// renders in a fixed 40px box (see .completed-item-thumb), and the source photos here can
|
||||||
|
// be full camera resolution, so fetching the original would be a lot of wasted bandwidth
|
||||||
|
// and decode work for a summary list. Mirrors FotoFirstBulkImportWorkflow.vue's own
|
||||||
|
// thumbnailPathForHash(); see docs/implementation.md#thumbnail-lookup-by-hash.
|
||||||
|
thumbnailPathForHash(hash, size = 64) {
|
||||||
|
return `/media/${size}/${hash.slice(0, 2)}/${hash.slice(2, 4)}/${hash.slice(4, 6)}/${hash.slice(6)}/`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<textarea v-if="type === 'textarea'" class="form-control" rows="2" v-model="localValue"></textarea>
|
||||||
|
|
||||||
|
<input v-else-if="type === 'number'" type="number" class="form-control" min="0" v-model.number="localValue">
|
||||||
|
|
||||||
|
<!-- Tags/properties mutate the passed-in array in place (same convention as
|
||||||
|
InventoryNew.vue's own `:value="item.tags"`), so no v-model wiring is needed here. -->
|
||||||
|
<tag-field v-else-if="type === 'tags'" :value="modelValue"></tag-field>
|
||||||
|
|
||||||
|
<property-field v-else-if="type === 'properties'" :value="modelValue"></property-field>
|
||||||
|
|
||||||
|
<select v-else-if="type === 'owner_group'" class="form-select" v-model="localValue">
|
||||||
|
<option :value="null">Myself</option>
|
||||||
|
<option v-for="group in ownerGroups" :key="group.handle" :value="group.handle">{{ group.handle }}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select v-else-if="type === 'availability_policy'" class="form-select" v-model="localValue">
|
||||||
|
<option v-for="policy in availabilityPolicies" :key="policy.slug" :value="policy.slug">
|
||||||
|
{{ policy.text }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select v-else-if="type === 'storage_location'" class="form-select" v-model="localValue">
|
||||||
|
<option :value="null">No storage location</option>
|
||||||
|
<option v-for="location in storageLocations" :key="location.id" :value="location.id">
|
||||||
|
{{ location.path }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input v-else type="text" class="form-control" v-model="localValue">
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TagField from "@/components/TagField.vue";
|
||||||
|
import PropertyField from "@/components/PropertyField.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FotoFirstFieldInput',
|
||||||
|
components: {TagField, PropertyField},
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
ownerGroups: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
availabilityPolicies: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
storageLocations: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
computed: {
|
||||||
|
localValue: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -45,7 +45,6 @@ export function buildWorkflowApiPayload(workflow) {
|
||||||
slug: workflow.slug,
|
slug: workflow.slug,
|
||||||
state: 'running',
|
state: 'running',
|
||||||
current_step: 1,
|
current_step: 1,
|
||||||
total_steps: workflow.stepDefinitions.length,
|
|
||||||
payload: {
|
payload: {
|
||||||
...ownPayload
|
...ownPayload
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue