Skip to content

Commit

Permalink
Add table showing number of lines per character per scene
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim020 committed Jun 28, 2023
1 parent 65e9e7a commit f0fe304
Show file tree
Hide file tree
Showing 3 changed files with 281 additions and 51 deletions.
113 changes: 62 additions & 51 deletions client/src/views/show/config/ConfigCharacters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,67 @@
>
<b-row>
<b-col>
<h5>Character List</h5>
<b-table
id="character-table"
:items="CHARACTER_LIST"
:fields="characterFields"
:per-page="rowsPerPage"
:current-page="currentPage"
show-empty
>
<template #head(btn)="data">
<b-button
v-b-modal.new-character
variant="outline-success"
<b-tabs content-class="mt-3">
<b-tab
title="Characters"
active
>
<b-table
id="character-table"
:items="CHARACTER_LIST"
:fields="characterFields"
:per-page="rowsPerPage"
:current-page="currentPage"
show-empty
>
New Character
</b-button>
</template>
<template #cell(cast_member)="data">
<template v-if="data.item.cast_member">
{{ data.item.cast_member.first_name }} {{ data.item.cast_member.last_name }}
</template>
<template v-else>
<b-link @click="openEditForm(data)">
Set Cast Member
</b-link>
</template>
</template>
<template #cell(btn)="data">
<b-button-group>
<b-button
variant="warning"
@click="openEditForm(data)"
>
Edit
</b-button>
<b-button
variant="danger"
@click="deleteCharacter(data)"
>
Delete
</b-button>
</b-button-group>
</template>
</b-table>
<b-pagination
v-show="CHARACTER_LIST.length > rowsPerPage"
v-model="currentPage"
:total-rows="CHARACTER_LIST.length"
:per-page="rowsPerPage"
aria-controls="character-table"
class="justify-content-center"
/>
<template #head(btn)="data">
<b-button
v-b-modal.new-character
variant="outline-success"
>
New Character
</b-button>
</template>
<template #cell(cast_member)="data">
<template v-if="data.item.cast_member">
{{ data.item.cast_member.first_name }} {{ data.item.cast_member.last_name }}
</template>
<template v-else>
<b-link @click="openEditForm(data)">
Set Cast Member
</b-link>
</template>
</template>
<template #cell(btn)="data">
<b-button-group>
<b-button
variant="warning"
@click="openEditForm(data)"
>
Edit
</b-button>
<b-button
variant="danger"
@click="deleteCharacter(data)"
>
Delete
</b-button>
</b-button-group>
</template>
</b-table>
<b-pagination
v-show="CHARACTER_LIST.length > rowsPerPage"
v-model="currentPage"
:total-rows="CHARACTER_LIST.length"
:per-page="rowsPerPage"
aria-controls="character-table"
class="justify-content-center"
/>
</b-tab>
<b-tab title="Line Counts">
<character-line-stats />
</b-tab>
</b-tabs>
</b-col>
</b-row>
<b-modal
Expand Down Expand Up @@ -176,9 +185,11 @@
<script>
import { required } from 'vuelidate/lib/validators';
import { mapGetters, mapActions } from 'vuex';
import CharacterLineStats from '@/vue_components/show/config/characters/CharacterLineStats.vue';
export default {
name: 'ConfigCharacters',
components: { CharacterLineStats },
data() {
return {
rowsPerPage: 15,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<template>
<b-container
class="mx-0 px-0"
fluid
>
<b-row>
<b-col>
<div
v-if="!loaded"
class="text-center center-spinner"
>
<b-spinner
style="width: 10rem; height: 10rem;"
variant="info"
/>
</div>
<template v-else-if="sortedScenes.length > 0">
<b-table
:items="tableData"
:fields="tableFields"
responsive
show-empty
sticky-header="65vh"
>
<template #thead-top="data">
<b-tr>
<b-th colspan="1">
<span class="sr-only">Character</span>
</b-th>
<template v-for="act in sortedActs">
<b-th
v-if="numScenesPerAct(act.id) > 0"
:key="act.id"
variant="primary"
:colspan="numScenesPerAct(act.id)"
class="act-header"
>
{{ act.name }}
</b-th>
</template>
</b-tr>
</template>
<template
v-for="scene in sortedScenes"
#[getHeaderName(scene.id)]="data"
>
{{ scene.name }}
</template>
<template #cell(Character)="data">
{{ CHARACTER_BY_ID(data.item.Character).name }}
</template>
<template
v-for="scene in sortedScenes"
#[getCellName(scene.id)]="data"
>
<template
v-if="getLineCountForCharacter(data.item.Character, scene.act, scene.id) > 0"
>
{{ getLineCountForCharacter(data.item.Character, scene.act, scene.id) }}
</template>
</template>
</b-table>
</template>
<b v-else>Unable to get mic allocations. Ensure act and scene ordering is set.</b>
</b-col>
</b-row>
</b-container>
</template>

<script>
import { mapActions, mapGetters } from 'vuex';
import { makeURL } from '@/js/utils';
import log from 'loglevel';
export default {
name: 'CharacterLineStats',
data() {
return {
loaded: false,
characterStats: {},
};
},
async mounted() {
await this.GET_ACT_LIST();
await this.GET_SCENE_LIST();
await this.getCharacterStats();
this.loaded = true;
},
methods: {
numScenesPerAct(actId) {
return this.sortedScenes.filter((scene) => scene.act === actId).length;
},
getHeaderName(sceneId) {
return `head(${sceneId})`;
},
getCellName(sceneId) {
return `cell(${sceneId})`;
},
async getCharacterStats() {
const response = await fetch(`${makeURL('/api/v1/show/character/stats')}`);
if (response.ok) {
this.characterStats = await response.json();
} else {
log.error('Unable to get character stats!');
}
},
getLineCountForCharacter(characterId, actId, sceneId) {
if (!Object.keys(this.characterStats).includes('line_counts')) {
return 0;
}
const lineCounts = this.characterStats.line_counts;
if (Object.keys(lineCounts).includes(characterId.toString())) {
const characterCounts = this.characterStats.line_counts[characterId];
if (Object.keys(characterCounts).includes(actId.toString())) {
const actCounts = characterCounts[actId];
if (Object.keys(actCounts).includes(sceneId.toString())) {
return actCounts[sceneId];
}
}
}
return 0;
},
...mapActions(['GET_ACT_LIST', 'GET_SCENE_LIST']),
},
computed: {
tableData() {
if (!this.loaded) {
return [];
}
return this.CHARACTER_LIST.map((character) => ({
Character: character.id,
}), this);
},
tableFields() {
return ['Character', ...this.sortedScenes.map((scene) => (scene.id.toString()))];
},
sortedActs() {
if (this.CURRENT_SHOW.first_act_id == null) {
return [];
}
let currentAct = this.ACT_BY_ID(this.CURRENT_SHOW.first_act_id);
if (currentAct == null) {
return [];
}
const acts = [];
while (currentAct != null) {
acts.push(currentAct);
currentAct = this.ACT_BY_ID(currentAct.next_act);
}
return acts;
},
sortedScenes() {
if (this.CURRENT_SHOW.first_act_id == null) {
return [];
}
let currentAct = this.ACT_BY_ID(this.CURRENT_SHOW.first_act_id);
if (currentAct == null || currentAct.first_scene == null) {
return [];
}
const scenes = [];
while (currentAct != null) {
let currentScene = this.SCENE_BY_ID(currentAct.first_scene);
while (currentScene != null) {
scenes.push(currentScene);
currentScene = this.SCENE_BY_ID(currentScene.next_scene);
}
currentAct = this.ACT_BY_ID(currentAct.next_act);
}
return scenes;
},
...mapGetters(['ACT_BY_ID', 'SCENE_BY_ID', 'CURRENT_SHOW', 'CHARACTER_BY_ID', 'CHARACTER_LIST']),
},
};
</script>
43 changes: 43 additions & 0 deletions server/controllers/api/show/characters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from collections import defaultdict

from tornado import escape

from models.script import Script, ScriptRevision, ScriptLine
from models.show import Show, Cast, Character, CharacterGroup
from rbac.role import Role
from schemas.schemas import CharacterSchema, CharacterGroupSchema
Expand Down Expand Up @@ -154,6 +157,46 @@ async def delete(self):
await self.finish({'message': '404 show not found'})


@ApiRoute('show/character/stats', ApiVersion.V1)
class CharacterStatsController(BaseAPIController):
async def get(self):
current_show = self.get_current_show()
show_id = current_show['id']

with self.make_session() as session:
show: Show = session.query(Show).get(show_id)
if show:
script: Script = session.query(Script).filter(Script.show_id == show.id).first()

if script.current_revision:
revision: ScriptRevision = session.query(ScriptRevision).get(
script.current_revision)
else:
self.set_status(400)
await self.finish({'message': 'Script does not have a current revision'})
return

line_counts = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
for line_association in revision.line_associations:
line: ScriptLine = line_association.line
if line.stage_direction:
continue
for line_part in line.line_parts:
if line_part.line_part_cuts is not None:
continue
if line_part.character_id:
line_counts[line_part.character_id][line.act_id][line.scene_id] += 1
elif line_part.character_group_id:
for character in line_part.character_group.characters:
line_counts[character.id][line.act_id][line.scene_id] += 1

self.set_status(200)
await self.finish({'line_counts': line_counts})
else:
self.set_status(404)
await self.finish({'message': '404 show not found'})


@ApiRoute('show/character/group', ApiVersion.V1)
class CharacterGroupController(BaseAPIController):

Expand Down

0 comments on commit f0fe304

Please sign in to comment.