Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/interviews/components/folder-picker/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>&lt;pages-tab&gt;</title>
</head>

<body>
<div id="demo-html" style="margin: 20px;"></div>

<script src="../../node_modules/steal/steal.js"
data-main="@empty">
</script>

<script type="steal-module">
import PagesTab from "~/src/pages-tab/"
import "~/styles/"
import "~/styles.less"

window.Languages = {
regional: {
en: { Language: "English", LanguageEN: "English", locale: "en" },
es: { Language: "Español", LanguageEN: "Spanish", locale: "es" },
fr: { Language: "Français", LanguageEN: "French", locale: "fr" }
},
set: (lang) => { console.log('demo setting lanugage to ' + lang)}
}

// replace any global guide with safe demo guide
window.gGuide = { title: 'canjs demo gGuide'}

const guide = {
title: 'The best GI',
version: '12/20/2020',
notes: 'Created at 12/20/2020',
sendfeedback: true,
emailContact: 'foo@bar.com',
logoImage: 'fancy logo',
endImage: 'custom courthouse',
description: 'some description',
jurisdiction: 'some jurisdiction',
guideGender: 'Female',
avatarSkinTone: 'darker',
avatarHairColor: 'red',
credits: 'some credits',
language: 'en',
completionTime: '42 minutes'
}

// passing the viewModel with guide data emulates stache bindings
const pagesComponent = new PagesTab({viewModel: {guide, guideId: 3}})
pagesComponent.connectedCallback = () => {}
const demoHtml = document.getElementById('demo-html')
demoHtml.appendChild(pagesComponent.element)
</script>
</body>
</html>
Empty file.
Empty file.
58 changes: 58 additions & 0 deletions src/interviews/components/folder-picker/folder-picker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import DefineMap from 'can-define/map/map'
// import DefineList from 'can-define/list/list'
import Component from 'can-component'
import template from './folder-picker.stache'

export const FolderPickerVM = DefineMap.extend('FolderPickerVM', {
guideListRow: {}, /* a row/record from CAJA_WS listGuides() api, meta data about the actual interview */
folders: {}, // a List in the shape = [ { path: 'foo/bar' }, ... ]
savedCallback: {},

get interviewFolder () {
return this.guideListRow && this.guideListRow.folder
},

folderSearch: {
value ({ lastSet, listenTo, resolve }) {
listenTo('interviewFolder', resolve)
listenTo(lastSet, resolve)
resolve(this.interviewFolder || '')
}
},

get foldersFiltered () {
const folders = this.folders || []
const filtered = folders.filter(f => {
const path = (f && f.path) || 'Unsorted'
return path.toLowerCase().indexOf(this.folderSearch.toLowerCase()) !== -1
})
return filtered
},

savePromise: {},

setFolderTo (path) {
// TODO: update this line to a promise returned from an API call that sets `this.guideListRow.folder` to `path` in the DB
const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve({success: true}) }, 1500) })

// TODO: The rest of this shouldn't need to change
this.savePromise = promise.then(() => {
// update local instance to match what we just saved to the DB
this.guideListRow.folder = path

// call the callback, which currently just closes this folder-picker instance
if (typeof this.savedCallback === 'function') {
this.savedCallback(path, this.guideListRow)
}
})

return this.savePromise
}
})

export default Component.extend({
tag: 'folder-picker',
view: template,
leakScope: false,
ViewModel: FolderPickerVM
})
43 changes: 43 additions & 0 deletions src/interviews/components/folder-picker/folder-picker.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@import (reference) '../../../../styles';

folder-picker {
display: block;
/* padding: @grid-gutter-width; */

.exact-match ~ .add-new-folder {
display: none;
}

ul, li {
width: 100%;
padding: 0px;
margin: 0px;
list-style: none;
}

ul {
position: relative;
height: 200px;
overflow-y: auto;
margin-top: 16px;
padding-bottom: 80px;
}

li {
margin-top: 8px;
}

ul .btn {
padding: 8px;
margin: 0px;
width: 100%;
text-align: left;
}

.add-new-folder {
width: ~"calc(100% - 28px)";
position: fixed;
bottom: 9px;
left: 14px;
}
}
29 changes: 29 additions & 0 deletions src/interviews/components/folder-picker/folder-picker.stache
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<can-import from="./folder-picker.less" />

{{#if(savePromise.isPending)}}
Saving...
{{else}}
<input type="text" value:bind="folderSearch" on:input="folderSearch = scope.element.value" class="form-control" placeholder="Unsorted">

<ul>
{{#for(folder of foldersFiltered)}}
<li class="{{#if(folderSearch)}}{{#is(folder.path, folderSearch)}}exact-match{{/is}}{{/if}}">
<button class="btn {{#is(folder.path, folderSearch)}}btn-primary{{else}}btn-default{{/is}}" on:click="setFolderTo(folder.path)">
<span aria-hidden="true" class="glyphicon-folder-upload"></span>
{{#if(folder.path)}}{{folder.path}}{{else}}Unsorted{{/if}}
</button>
</li>
{{/for}}
{{#if(folderSearch)}}
{{^is(folderSearch, "Unsorted")}}
<li class="add-new-folder">
<button class="btn btn-primary" on:click="setFolderTo(folder.path)">
Place in New Folder:<br>
<span aria-hidden="true" class="glyphicon-folder-open"></span>
<b>{{folderSearch}}</b>
</button>
</li>
{{/is}}
{{/if}}
</ul>
{{/if}}
78 changes: 77 additions & 1 deletion src/interviews/interviews.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import $ from 'jquery'
import DefineMap from 'can-define/map/map'
import DefineList from 'can-define/list/list'
import Component from 'can-component'
import Guide from 'a2jauthor/src/models/guide'
import template from './interviews.stache'

const ObservableProxy = DefineMap.extend('ObservableProxy', {
obj: {},
key: {},
value: {
value ({ lastSet, listenTo, resolve }) {
listenTo(lastSet, function (val) {
this.obj[this.key] = val
resolve(val)
})
resolve(this.obj[this.key])
}
}
})

export const InterviewsVM = DefineMap.extend('InterviewsVM', {
// passed in via author app.stache
previewInterview: {},
Expand All @@ -16,9 +31,13 @@ export const InterviewsVM = DefineMap.extend('InterviewsVM', {
}
},

interviews: {
interviews: { /* all rows/records from CAJA_WS listGuides() api, meta data about the actual interviews */
value ({ lastSet, listenTo, resolve }) {
this.interviewsPromise.then((interviews) => {

// TODO: remove this test line when the 'folder' column is reutrned from listGuides() API
interviews.forEach(f => (f.folder = ['', 'foo', 'bar', 'Test Folder', 'test/folder/path', 'just a string'][~~(Math.random() * 6)]))

resolve(interviews)
})

Expand All @@ -40,6 +59,63 @@ export const InterviewsVM = DefineMap.extend('InterviewsVM', {
}
},

isUnsorted (path) {
return !path
},
newObservableBool (tf = false) {
return new DefineMap({ value: tf })
},
toggleBool (observableBool) {
observableBool.value = !observableBool.value
},

// bound and passed to folder-picker as the savedCallback param
forceFolderUpdate (observableBool) {
observableBool.value = false // close this folder-picker
this.ownedInterviewsByFolder = this.interviews // force ownedInterviewsByFolder to recalc
},

// folders = [ { path: 'foobar', ls: [] }, ... ]
// TODO: this is done, the CAJA_WS listGuides() api just needs to return the "folder" property along with the rest of the meta data
ownedInterviewsByFolder: {
value ({ lastSet, listenTo, resolve }) {
const resolver = interviews => {
if (interviews && interviews.value && interviews.value.length) {
interviews = interviews.value // old can model weirdness?
}
const folders = new DefineList()
const folderMap = {}
const ownedList = (interviews && interviews.owned && interviews.owned()) || []
ownedList.forEach(i => {
const path = i.folder || '' // guideListRow.folder is just a string
const folderMeta = folderMap[path] = (folderMap[path] || new DefineMap())
if (!folderMeta.ls) {
folderMeta.path = path
folderMeta.ls = new DefineList()
folders.push(folderMeta)
}
folderMeta.ls.push(i)
})
resolve(folders)
}
listenTo('interviews', resolver)
listenTo('interviews.lenth', () => resolver(this.interviews))
listenTo(lastSet, resolver)
resolver([])
}
},

newObservableProxy (obj, key) {
return new ObservableProxy({ obj, key })
},
folderPopoverTitle (interviewTitle) {
let title = interviewTitle || ''
if (title.length > 53) {
title = title.substr(0, 50) + '...'
}
return `Move Interview "${title}" into folder`
},

saveCurrentGuidePromise: {
get () {
// this assures any changes to current guide are saved before loading
Expand Down
30 changes: 30 additions & 0 deletions src/interviews/interviews.less
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
interviews-page {
display: block;

legend button,
.change-folder {
background: none;
border: none;
padding: 0;
margin: 0;
}
.change-folder {
margin-left: 8px;
opacity: 0.4;
&:hover {
opacity: 1;
}
}
.folder-item {
display: grid;
grid-template-columns: auto 1fr;
position: relative;

.popover {
left: 30px;
top: 50%;
transform: translateY(-50%);
}

.guide {
margin-bottom: 0px;
}
}
}
47 changes: 39 additions & 8 deletions src/interviews/interviews.stache
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<can-import from="a2jauthor/src/loading/"/>
<can-import from="a2jauthor/src/interviews/interviews.less!"/>
<can-import from="a2jauthor/src/popover/" />
<can-import from="a2jauthor/src/interviews/components/folder-picker/"/>

{{#if(interviewsPromise.isPending)}}
<app-loading loadingMessage:from="loadingMessage"></app-loading>
Expand All @@ -24,14 +26,43 @@
<fieldset>
<legend>Edit one of my interviews</legend>

<div class="list-group">
{{#each(interviews.owned())}}
<a class="guide list-group-item {{#eq(id, scope.vm.currentGuideId)}}guide-opened{{/eq}}" gid="{{id}}">
<span class="title">{{title}}</span>
<small class="pull-right text-muted">#{{id}} | {{formatFileSize fileSize}} | {{lastModified}}</small>
</a>
{{/each}}
</div>
{{#for(folder of ownedInterviewsByFolder)}}
<fieldset>
{{let expanded = newObservableBool(isUnsorted(folder.path))}}
<legend>
<button on:click="toggleBool(expanded)" aria-label="{{#if(expanded.value)}}hide{{else}}show{{/if}} interviews">
<span aria-hidden="true" class="{{#if(expanded.value)}}glyphicon-folder-open{{else}}glyphicon-folder{{/if}}"></span>
({{folder.ls.length}})
{{#if(folder.path)}}{{folder.path}}{{else}}Unsorted{{/if}}
</button>
</legend>
<div class="list-group" {{^if(expanded.value)}}style="display: none;"{{/if}}>
{{#for(guideListRow of folder.ls)}}
<div class="folder-item">
{{let pickerShown = newObservableBool(false)}}
<button class="change-folder" title="Move into folder..." ariia-label="Move into folder..." on:click="toggleBool(pickerShown)">
<span aria-hidden="true" class="glyphicon-step-zero"></span>
</button>
<a class="guide list-group-item {{#eq(guideListRow.id, scope.vm.currentGuideId)}}guide-opened{{/eq}}" gid="{{guideListRow.id}}">
<span class="title">{{guideListRow.title}}</span>
<small class="pull-right text-muted">
#{{guideListRow.id}} | {{formatFileSize guideListRow.fileSize}} | {{guideListRow.lastModified}}
</small>
</a>
{{#if(pickerShown.value)}}
<app-popover title:from="folderPopoverTitle(guideListRow.title)">
<folder-picker
guideListRow:bind="guideListRow"
folders:bind="ownedInterviewsByFolder"
savedCallback:from="forceFolderUpdate.bind(scope.vm, pickerShown)"
/>
</app-popover>
{{/if}}
</div>
{{/for}}
</div>
</fieldset>
{{/for}}
</fieldset>

<fieldset>
Expand Down