~hokiegeek/crooked-monkey-vue

42f7f68c6b13ef19ffe5c064a467523ad3e8c4d4 — HokieGeek 2 months ago c05ba47 main
This mess of changes is my first stab of actually displaying programs in some sort of way
R src/components/session/session-item.vue => src/components/collection-item.vue +9 -3
@@ 1,7 1,11 @@
<template>
  <span v-if="data">
    <span v-if="kind == 'video'">
      <video-item-entry :video="data" size="medium"/>
      <video-item-entry v-if="layout == 'horizontal'" :video="data" size="medium"/>
      <video-card v-else
        :video="data"
        size="small"
      />
    </span>
    <span v-if="kind == 'collection'">
      <session-card :session="data" size="medium"/>


@@ 12,12 16,14 @@
<script lang="ts">
import { defineComponent } from 'vue';
import videoItemEntry from '@/components/video/video-item-entry.vue';
import videoCard from '@/components/video/video-card.vue';
import sessionCard from '@/components/session/session-card.vue';
export default defineComponent({
  name: "session-item",
  props: ['kind','data'],
  name: 'collection-item',
  props: ['kind','data','layout'],
  components: {
    videoItemEntry,
    videoCard,
    sessionCard,
  },
  setup () {

A src/components/collection-items-column.vue => src/components/collection-items-column.vue +52 -0
@@ 0,0 1,52 @@
<template>
  <div class="column">
    <collection-item
      v-for="item in items" :key="item.id"
      :kind="item.kind" :data="item.data"
      :layout="layout"
      />
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import collectionItem from '@/components/collection-item.vue';

export default defineComponent({
  name: 'collection-items-column',
  props: ['items', 'layout'],
  components: {
    collectionItem,
  },
  setup () {

    return {
    }
  }
});
</script>

<style lang="scss" scoped>
/*
:deep(.card) {
  background-color: transparent !important;

  .card-image:first-child img {
    border-radius: 0.125rem;
  }

  .card-content {
    padding: .5rem;
    padding-top: .25rem;

    .title {
      font-size: .9rem;
      font-weight: 400;
      line-height: 1.5rem;
      max-height: 4rem;
      color: #f5eefd;
    }
  }
}
*/
</style>

M src/components/list-editor/controls.vue => src/components/list-editor/controls.vue +7 -4
@@ 15,7 15,7 @@
<script lang="ts">
import { defineComponent, inject, ref } from 'vue';
import { Auth } from '@/services/auth';
import { YTPulseApi, CrookedSession } from '@/services/ytpulse';
import { YTPulseApi, CrookedSession, YTPulseCollectionItem } from '@/services/ytpulse';
import { SelectedQueue } from '@/services/selected-queue';
import { Notifier } from '@/services/notifier';
import { useRouter } from 'vue-router'


@@ 69,7 69,7 @@ export default defineComponent({
      return Promise.reject(new Error('no valid user'))
    }

    const addQueueItemsToSession = async (sessionId: string, sessionTitle: string, items: Array<any>) => { // eslint-disable-line
    const addQueueItemsToSession = async (sessionId: string, sessionTitle: string, items: Array<YTPulseCollectionItem>) => {
          console.log('addQueueItemsToSession: items = ', items)
      if (selectedQueue) {
        const user = auth?.getUser()


@@ 82,6 82,7 @@ export default defineComponent({
                kind: item.kind,
                id: realItem.id,
                position: items.length,
                data: '',
              })
            }
          }


@@ 106,8 107,10 @@ export default defineComponent({
        if (user) {
          const sessionItems = await ytpulse?.getSessionItems(user, sessionId)

          console.log('addToExistingSession: adding queue')
          await addQueueItemsToSession(sessionId, sessionTitle, sessionItems.items)
          if (sessionItems?.items) {
            console.log('addToExistingSession: adding queue')
            await addQueueItemsToSession(sessionId, sessionTitle, sessionItems?.items)
          }
        }
      }
    }

A src/components/program/day-column.vue => src/components/program/day-column.vue +186 -0
@@ 0,0 1,186 @@
<template>
    <!-- div class="column">
      {{ `${day}: ${dayId} (${programId})` }}
      <div v-for="item in items" :key="item.id">
          {{ `(${item.kind}) ${item.id}` }}
      </div>
    </!-->
    <collection-items-column :items="items" layout="vertical" />
    <!--
      <session-item v-for="item in items" :key="item.id" :kind="item.kind" :data="item.data"/>
      <tags size="medium" :tags="data.userData.tags" @update="updateTags" :readonly="!auth.isAuthorized()"/>

      <div>rating: {{ data.userData.rating }}</div>
      <div>effort: {{ data.userData.effort }}</div>
      <div>skillLevel: {{ data.userData.skillLevel }}</div>
      -->

</template>

<script lang="ts">
import { defineComponent, ref, inject, Ref, watchEffect } from 'vue';
import { useRouter } from 'vue-router'
import { Auth } from '@/services/auth';
import { YTPulseApi, YTPulseCollectionItem, YTPulseAuthError } from '@/services/ytpulse';
import collectionItemsColumn from '@/components/collection-items-column.vue';
/*
import description from '@/components/description.vue';
import thumbnail from '@/components/thumbnail.vue';
import tags from '@/components/tags.vue';
import privacyStatusSymbol from '@/components/privacy-status-symbol.vue';
*/

/*
class YtPulseProgramDayItem {
	kind: string;
	id: string;
	position: number;
  data: string;

  constructor(kind: string, id: string, position: number, data: string) {
    this.kind = kind;
    this.id = id;
    this.position = position;
    this.data = data;
  }
}
*/

export default defineComponent({
  name: 'program-day-column',
  props: ['programId', 'dayId', 'day'],
  components: {
    collectionItemsColumn,
      /*
    description,
    thumbnail,
    tags,
    privacyStatusSymbol,
    */
  },
  setup (props) {
    console.log(`column for day ${props.day} (${props.dayId})`)
    const programDay = ref()
    const items: Ref<YTPulseCollectionItem[]> = ref([])
    const auth: Auth | undefined = inject('auth');
    const ytpulse: YTPulseApi | undefined = inject('ytpulse');
    const router = useRouter()

    const updateTags = (tags: Array<string>) => {
      /*
      const user = auth?.getUser()
      if (user && props.data) {
        const vid = props.data
        vid.userData.tags = tags
        ytpulse?.updateSession(user, vid.id, vid)
        .then(response => {
          if (response.status == 200)  {
            session.value.userData.tags = tags
          } else {
            console.error("wtf?")
          }
        })
        .catch(error => {
          console.error(error)
        })
      }
      */
    }

    watchEffect(async () => {
      if (auth?.isAuthorized()) {
        const user = auth?.getUser()
        if (user && props.programId) {
          let needsAuth = false

          await ytpulse?.getProgramDayItems(user, props.programId, props.day)
          .then(response => {
            console.log(`day ${props.day} items?`, response)

            response.items.forEach((item: YTPulseCollectionItem) => {
                /*
              console.log("el", item) 
              console.log("   pos", item.position) 
              console.log("   kind", item.kind) 
              console.log("   id", item.id) 
              */
              items.value[item.position] = {
                kind: item.kind,
                position: item.position,
                id: item.id,
                data: '',
              }
              let p;
              switch(item.kind) {
                case 'collection':
                  p = ytpulse?.getSession(user, item.id, false)
                  break
                default: // video
                  p = ytpulse?.getVideo(user, item.id, false)
                  break
              }
              p
              .then(response => {
                items.value[item.position].data = response
                console.log("afp: DAY ITEM: %s %s:", item.kind, item.id, items.value[item.position].data)
              })
              .catch(error => {
                if ((error instanceof YTPulseAuthError) && !needsAuth) {
                  console.warn('unauthorized error. routing to signin')
                  needsAuth = true
                  router.push({
                    name: 'signin', query: {
                      authorize: 1,
                      redirect: router.currentRoute.value.fullPath,
                    }
                  })
                } else {
                  console.error(error)
                }
              })
            });
          })
          .catch(error => {
            console.error("crap: ", error)
          })
        }
      }
    })

    return {
     programDay,
     items,
        /*
      auth,
      updateTags,
      */
    }
  }
});
</script>

<style lang="scss" scoped>
/*
:deep(.card) {
  background-color: transparent !important;

  .card-image:first-child img {
    border-radius: 0.125rem;
  }

  .card-content {
    padding: .5rem;
    padding-top: .25rem;

    .title {
      font-size: .9rem;
      font-weight: 400;
      line-height: 1.5rem;
      max-height: 4rem;
      color: #f5eefd;
    }
  }
}
*/
</style>


M src/components/program/details.vue => src/components/program/details.vue +63 -171
@@ 1,85 1,78 @@
<template>
  <div v-if="data" class="columns is-centered p-2">
    <div class="column is-one-third" style="background-color: #0a0a0c; height: 100vh">
      <div class="columns">

        <!-- div class="column">
          <div style="position: relative">
            <a :href="data.url" target="_blank">
              <thumbnail :item="data" size="medium" overlay/>
            </a>
          </div>
        </!-->

        <!-- div class="column">
          <div class="subtitle">{{ data.title }}</div>
          <div>channel: {{ data.channelId }}</div>
          <div>
            <privacy-status-symbol :status="data.privacyStatus"/> {{ data.publishedAt }}
            <span v-if="data.licensedContent">
              under {{ data.license }}
            </span>
          </div>
        </!-->
  <div v-if="program">
    <div class="columns">
      <div class="column is-narrow">
        <div style="position: relative; width: 200px">
          <a :href="program.url" target="_blank">
            <thumbnail :item="program" size="small" overlay/>
          </a>
        </div>
      </div>

      <div class="column">
        <div class="title">{{ program.title }}</div>
        <div>
          <privacy-status-symbol :status="program.privacyStatus"/> {{ program.publishedAt }}
          <span v-if="program.licensedContent">
            under {{ program.license }}
          </span>
        </div>
      </div>

    </div>

      <!--
      <article class="message" v-if="data.userData.comments">
      <article class="message" v-if="program.userData.comments">
        <div class="message-body has-background-dark">
          <description :text="data.userData.comments" />
          <description :text="program.userData.comments" />
        </div>
      </article>

      <article class="message">
        <div class="message-body has-background-dark">
          <description :text="data.description" />
          <description :text="program.description" />
        </div>
      </article>

      <div>linked: {{ data.userLinked }}</div>
      <div>activity type: {{ data.userData.activityType }}</div>
      <tags size="medium" :tags="data.userData.tags" @update="updateTags" :readonly="!auth.isAuthorized()"/>
      <div>linked: {{ program.userLinked }}</div>
      <tags size="medium" :tags="program.userData.tags" @update="updateTags" :readonly="!auth.isAuthorized()"/>

      <div>rating: {{ data.userData.rating }}</div>
      <div>effort: {{ data.userData.effort }}</div>
      <div>skillLevel: {{ data.userData.skillLevel }}</div>
      <div>rating: {{ program.userData.rating }}</div>
      <div>effort: {{ program.userData.effort }}</div>
      <div>skillLevel: {{ program.userData.skillLevel }}</div>
      -->
    </div>

    <div class="column">
      <!-- session-item v-for="item in items" :key="item.id" :kind="item.kind" :data="item.data"/-->
    </div>
    <section class="columns">
      <program-day-column
        v-for="day in days" :key="day.id"
        :programId="data.id" :dayId="day.id" :day="day.position"
        class="day"
        />
    </section>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, onMounted, inject, Ref, watchEffect } from 'vue';
import { defineComponent, ref, inject, Ref, watchEffect } from 'vue';
import { useRouter } from 'vue-router'
import { Auth } from '@/services/auth';
import { YTPulseApi, YTPulseAuthError } from '@/services/ytpulse';
import description from '@/components/description.vue';
import thumbnail from '@/components/thumbnail.vue';
// import sessionItem from '@/components/session/session-item.vue';
import privacyStatusSymbol from '@/components/privacy-status-symbol.vue';
import programDayColumn from '@/components/program/day-column.vue';

class YtPulseSessionItem {
	kind: string;
class YtPulseProgramDay {
	id: string;
	position: number;
  data: string;

  constructor(kind: string, id: string, position: number, data: string) {
    this.kind = kind;
  constructor(id: string, position: number) {
    this.id = id;
    this.position = position;
    this.data = data;
  }
}

export default defineComponent({
  name: 'program-details',
  // props: ['data'],
  props: {
    data: {
      type: Object


@@ 87,51 80,29 @@ export default defineComponent({
  },
  components: {
    // description,
    // thumbnail,
    // sessionItem,
    // privacyStatusSymbol,
    thumbnail,
    privacyStatusSymbol,
    programDayColumn,
  },
  setup (props) {
    const program = ref()
    const items: Ref<YtPulseSessionItem[]> = ref([])
    const days: Ref<YtPulseProgramDay[]> = ref([])
    const auth: Auth | undefined = inject('auth');
    const ytpulse: YTPulseApi | undefined = inject('ytpulse');
    const router = useRouter()

    watchEffect(() => program.value = props.data)

/*
    const video = async (id: string): Promise<any> => {
      // return 'TODO: [video] ' + id
          const user = auth?.getUser()
          if (user) {
            await ytpulse?.getVideo(user, id, false)
            .then(response => {
              console.log("vid()", response)
              return response
            })
            .catch(error => {
              console.error(error)
            })
          }
          return 'shit'
    }

    const session = computed((id: string) => {
      return 'TODO: [session] ' + id
    })
    */

    const updateTags = (tags: Array<string>) => {
      /*
      const user = auth?.getUser()
      if (user && props.data) {
        const vid = props.data
        vid.userData.tags = tags
        ytpulse?.updateVideo(user, vid.id, vid)
        const prog = props.data
        prog.userData.tags = tags
        ytpulse?.updateProgram(user, prog.id, prog)
        .then(response => {
          if (response.status == 200)  {
            session.value.userData.tags = tags
            program.value.userData.tags = tags
          } else {
            console.error("wtf?")
          }


@@ 143,110 114,26 @@ export default defineComponent({
      */
    }

/*
    onMounted(() => {
      console.log("afp: data: ", props.data)
      const seshget = setInterval(function () {
        if (auth?.isAuthorized()) {
          const user = auth?.getUser()
          if (user && props.data) {
            ytpulse?.getSessionItems(user, props.data.id)
            .then(response => {
              // items.value = response.data
              // itemsResp = response.data
              // response.data.items.forEach((item: YtPulseSessionItem) => {
              console.log("items?", response)
              response.items.forEach((item: YtPulseSessionItem) => {
                // console.log("el", item) 
                items.value[item.position] = {
                  kind: item.kind,
                  position: item.position,
                  id: item.id,
                  data: '',
                }
                let p;
                switch(item.kind) {
                  case 'collection':
                    p = ytpulse?.getSession(user, item.id, false)
                    break
                  default: // video
                    p = ytpulse?.getVideo(user, item.id, false)
                    break
                }
                p
                .then(response => {
                  items.value[item.position].data = response
                  // console.log("%s %s:", item.kind, item.id, items.value[item.position].data)
                })
                .catch(error => {
                  console.error(error)
                })

              });
            })
            .catch(error => {
              console.error(error)
            })
            clearInterval(seshget)
          }
        }
      }.bind(this), 1000); 
    })
    */


    watchEffect(() => {
      if (auth?.isAuthorized() && props.data) {
        console.log('afp: thumbs: ', program.value.thumbnails)
        const user = auth?.getUser()
        if (user) {
          console.log("afp: props.data.id", props.data.id, program.value)
          console.log("afp: program.id", program.value.id)
          ytpulse?.getProgramDays(user, props.data.id)
          .then(response => {
            console.log("days?", response)
            /*
            response.items.forEach((item: YtPulseSessionItem) => {
              console.log("el", item) 
              items.value[item.position] = {
                kind: item.kind,
                position: item.position,
                id: item.id,
                data: '',
              }
              let p;
              switch(item.kind) {
                case 'collection':
                  p = ytpulse?.getSession(user, item.id, false)
                  break
                default: // video
                  p = ytpulse?.getVideo(user, item.id, false)
                  break
              }
              p
              .then(response => {
                items.value[item.position].data = response
                console.log("afp: ITEM: %s %s:", item.kind, item.id, items.value[item.position].data)
              })
              .catch(error => {
                if (error instanceof YTPulseAuthError) {
                  // FIXME: this generates a dumb path as it aggregates all the queries to all  items which are found
                  console.warn('unauthorized error. routing to signin')
                  router.push({
                    name: 'signin', query: {
                      authorize: 1,
                      redirect: router.currentRoute.value.fullPath,
                    }
                  })
                } else {
                  console.error(error)
                }
              })

            });
            */
            days.value = response.days.sort((a: YtPulseProgramDay, b: YtPulseProgramDay) => a.position - b.position)
          })
          .catch(error => {
            console.error("crap: ", error)
            if (error instanceof YTPulseAuthError) {
              console.warn('unauthorized error. routing to signin')
              router.push({
                name: 'signin', query: {
                  authorize: 1,
                  redirect: router.currentRoute.value.fullPath,
                }
              })
            }
          })
        }
      }


@@ 254,7 141,8 @@ export default defineComponent({

    return {
      auth,
      items,
      program,
      days,
      updateTags,
    }
  }


@@ 283,4 171,8 @@ export default defineComponent({
    }
  }
}

.day {
  background-color: rgb(10, 10, 12);
}
</style>

M src/components/session/session-details.vue => src/components/session/session-details.vue +35 -15
@@ 48,9 48,7 @@
      <div>skillLevel: {{ data.userData.skillLevel }}</div>
    </div>

    <div class="column">
      <session-item v-for="item in items" :key="item.id" :kind="item.kind" :data="item.data"/>
    </div>
    <collection-items-column :items="items" layout="horizontal" />
  </div>
</template>



@@ 58,18 56,14 @@
import { defineComponent, ref, inject, Ref, watchEffect } from 'vue';
import { useRouter } from 'vue-router'
import { Auth } from '@/services/auth';
import { YTPulseApi, YTPulseAuthError } from '@/services/ytpulse';
import { YTPulseApi, YTPulseCollectionItem, YTPulseAuthError } from '@/services/ytpulse';
import description from '@/components/description.vue';
import thumbnail from '@/components/thumbnail.vue';
import tags from '@/components/tags.vue';
import sessionItem from '@/components/session/session-item.vue';
import collectionItemsColumn from '@/components/collection-items-column.vue';
import privacyStatusSymbol from '@/components/privacy-status-symbol.vue';

/*
class SessionItem {
  kind: string
}
*/
class YtPulseSessionItem {
	kind: string;
	id: string;


@@ 83,6 77,7 @@ class YtPulseSessionItem {
    this.data = data;
  }
}
*/

export default defineComponent({
  name: 'session_details',


@@ 91,12 86,12 @@ export default defineComponent({
    description,
    thumbnail,
    tags,
    sessionItem,
    collectionItemsColumn,
    privacyStatusSymbol,
  },
  setup (props) {
    const session = ref()
    const items: Ref<YtPulseSessionItem[]> = ref([])
    const items: Ref<YTPulseCollectionItem[]> = ref([])
    const auth: Auth | undefined = inject('auth');
    const ytpulse: YTPulseApi | undefined = inject('ytpulse');
    const router = useRouter()


@@ 147,14 142,23 @@ export default defineComponent({
      */
    }

    watchEffect(() => {
/*
    const populateItems = () => {

    }
    */

    watchEffect(async () => {
      if (auth?.isAuthorized()) {
        const user = auth?.getUser()
        if (user && props.data) {
          ytpulse?.getSessionItems(user, props.data.id)
          let needsAuth = false

          await ytpulse?.getSessionItems(user, props.data.id)
          .then(response => {
            console.log("items?", response)
            response.items.forEach((item: YtPulseSessionItem) => {

            response.items.forEach((item: YTPulseCollectionItem) => {
              console.log("el", item) 
              items.value[item.position] = {
                kind: item.kind,


@@ 177,15 181,18 @@ export default defineComponent({
                console.log("afp: ITEM: %s %s:", item.kind, item.id, items.value[item.position].data)
              })
              .catch(error => {
                if (error instanceof YTPulseAuthError) {
                if ((error instanceof YTPulseAuthError) && !needsAuth) {
                  // FIXME: this generates a dumb path as it aggregates all the queries to all  items which are found
                  console.warn('unauthorized error. routing to signin')
                  needsAuth = true
                  router.push({
                    name: 'signin', query: {
                      authorize: 1,
                      redirect: router.currentRoute.value.fullPath,
                    }
                  })
                  /*
                  */
                } else {
                  console.error(error)
                }


@@ 196,6 203,19 @@ export default defineComponent({
          .catch(error => {
            console.error("crap: ", error)
          })

          if (needsAuth) {
            console.warn('user is unauthorized. routing to signin')
            router.push({
              name: 'signin', query: {
                authorize: 1,
                redirect: router.currentRoute.value.fullPath,
              }
            })
          } else {
            console.error('shit')
          }

        }
      }
    })

M src/services/ytpulse.d.ts => src/services/ytpulse.d.ts +7 -0
@@ 22,4 22,11 @@ declare module 'ytpulse' {
        skillLevel: number;
        items: Array<CrookedSessionItem>;
    }

    interface YTPulseCollectionItem {
    	kind: string;
    	id: string;
    	position: number;
        data: string;
    }
}

M src/services/ytpulse.ts => src/services/ytpulse.ts +25 -10
@@ 27,6 27,25 @@ export interface CrookedSession {
  items: Array<CrookedSessionItem>;
}

export interface YTPulseCollectionItem {
	kind: string;
	id: string;
	position: number;
  data: string;
}

export interface YTPulseGetSessionItemsResponse {
	sessionId: string;
	items: Array<YTPulseCollectionItem>;
	data: string;
}

export interface YTPulseGetProgramDayItemsResponse {
	programDayId: string;
	items: Array<YTPulseCollectionItem>;
	data: string;
}

const cmsessionToSession = (cmsession: CrookedSession): any => { // eslint-disable-line
  return {
    source: 'crooked-monkey',


@@ 226,10 245,9 @@ export class YTPulseApi {
      return Promise.resolve(session)
    }

    async getSessionItems(user: User, id: string): Promise<any> { // eslint-disable-line
      let items;
      await httpGet(`/sessions/${ id }/items`, user)
                      .then(response => items = response.data)
    async getSessionItems(user: User, id: string): Promise<YTPulseGetSessionItemsResponse> {
      return await httpGet(`/sessions/${ id }/items`, user)
                      .then(response => Promise.resolve(response.data))
                      .catch(error => {
                        if (error.response.status == 401) {
                          return Promise.reject(new YTPulseAuthError(error.response.statusText))


@@ 237,7 255,6 @@ export class YTPulseApi {
                          return Promise.reject(error)
                        }
                      })
      return Promise.resolve(items)
    }

    async createSession(user: User, cmsession: CrookedSession): Promise<any> { // eslint-disable-line


@@ 320,10 337,9 @@ export class YTPulseApi {
      return Promise.resolve(days)
    }

    async getProgramDayItems(user: User, pid: string, day: number): Promise<any> { // eslint-disable-line
      let items;
      await httpGet(`/programs/${ pid }/days/${ day }/items`, user)
                      .then(response => items = response.data)
    async getProgramDayItems(user: User, pid: string, day: number): Promise<YTPulseGetProgramDayItemsResponse> {
      return await httpGet(`/programs/${ pid }/days/${ day }/items`, user)
                      .then(response => Promise.resolve(response.data))
                      .catch(error => {
                        if (error.response.status == 401) {
                          return Promise.reject(new YTPulseAuthError(error.response.statusText))


@@ 331,7 347,6 @@ export class YTPulseApi {
                          return Promise.reject(error)
                        }
                      })
      return Promise.resolve(items)
    }
}


M src/views/login.vue => src/views/login.vue +1 -2
@@ 50,8 50,7 @@ export default defineComponent({
              ytpulse?.createAndAuthorizeUser(user, email, authCode)
                .then(() => {
                  if (router.currentRoute.value.query.redirect) {
                    console.log("going back?", String(router.currentRoute.value.query.redirect))
                    // router.push({ path: String(router.currentRoute.value.query.redirect) })
                    router.push({ path: String(router.currentRoute.value.query.redirect) })
                  } else if (router.currentRoute.value.path === '/') {
                    router.push('videos')
                  }