vuetify 0.14.2

This commit is contained in:
Andy Bunce 2017-07-18 10:04:25 +01:00
parent efc929ca69
commit e5f691bf6c
24 changed files with 1078 additions and 197 deletions

View file

@ -54,6 +54,7 @@ const router = new VueRouter({
{ path: '/eval', component: Eval,meta:{title:"Evaluate XQuery"} },
{ path: '/logs', component: Log,meta:{title:"Server logs"} },
{ path: '/tasks', component: Task,meta:{title:"Runnable tasks"} },
{ path: '/tasks/model', component: Model,meta:{title:"build model"} },
{ path: '/jobs', component: Job,meta:{title:"Jobs"} },
{ path: '*', component: Notfound,meta:{title:"Page not found"} }
],
@ -94,42 +95,42 @@ const app = new Vue({
text: 'Collections' ,
model: false,
children: [
{href: 'database', text: 'Databases',icon: 'account_balance' },
{href: 'files', text: 'File system',icon: 'folder' },
{href: 'edit',text: 'edit',icon: 'mode_edit'},
{href: 'history',text: 'history',icon: 'history'},
{href: 'logs',text: 'Server logs',icon: 'dns'}
{href: '/database', text: 'Databases',icon: 'account_balance' },
{href: '/files', text: 'File system',icon: 'folder' },
{href: '/edit',text: 'edit',icon: 'mode_edit'},
{href: '/history',text: 'history',icon: 'history'},
{href: '/logs',text: 'Server logs',icon: 'dns'}
]},
{
icon: 'directions_run',
text: 'Actions' ,
model: false,
children: [
{href: 'eval',text: 'Query',icon: 'play_circle_outline'},
{href: 'jobs',text: 'Running jobs',icon: 'dashboard'},
{href: 'tasks',text: 'Tasks',icon: 'history'},
{href: '/eval',text: 'Query',icon: 'play_circle_outline'},
{href: '/jobs',text: 'Running jobs',icon: 'dashboard'},
{href: '/tasks',text: 'Tasks',icon: 'history'}
]},
{
icon: 'more_horiz',
text: 'More' ,
model: false,
children: [
{href: 'people',text: 'People',icon: 'person'},
{href: 'select',text: 'select',icon: 'extension'},
{href: 'puzzle',text: 'Puzzle',icon: 'extension'},
{href: 'images',text: 'Images',icon: 'camera_roll'},
{href: 'tabs',text: 'tabs',icon: 'switch_camera'},
{href: 'ping',text: 'ping',icon: 'update'},
{href: 'thumbnail',text: 'thumbnail',icon: 'touch_app'}
{href: '/people',text: 'People',icon: 'person'},
{href: '/select',text: 'select',icon: 'extension'},
{href: '/puzzle',text: 'Puzzle',icon: 'extension'},
{href: '/images',text: 'Images',icon: 'camera_roll'},
{href: '/tabs',text: 'tabs',icon: 'switch_camera'},
{href: '/ping',text: 'ping',icon: 'update'},
{href: '/thumbnail',text: 'thumbnail',icon: 'touch_app'}
]},
{href: 'settings',text: 'settings',icon: 'settings' }
{href: '/settings',text: 'settings',icon: 'settings' }
]
}},
methods: {
search(){
this.$router.push({path: 'search',query: { q: this.q }})
this.$router.push({path: '/search',query: { q: this.q }})
},
logout(){
HTTP.get("logout").then(r=>{

View file

@ -30,9 +30,7 @@
<v-list-tile-action>
<v-icon>{{ item.model ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
</v-list-tile-content>
</v-list-tile>
<template v-for="(child, i) in item.children" >
<v-list-tile :to="child.href" :key="i" ripple>

View file

@ -2,8 +2,8 @@
abbrev="vue-poc" version="0.0.3" spec="1.0">
<title>vue-poc test of vue.js.</title>
<dependency name="ace" version="1.2.7" />
<dependency name="vuetify" version="0.13.0" />
<dependency name="vue" version="2.3.4" />
<dependency name="vuetify" version="0.14.2" />
<dependency name="vue" version="2.4.1" />
<dependency name="vue-router" version="2.5.3" />
<dependency name="google-material" version="0.0.0" />
<dependency name="js-beautify" version="1.6.12" />

View file

@ -5,45 +5,9 @@
:)
module namespace vue-api = 'quodatum:vue.api.collection';
import module namespace rest = "http://exquery.org/ns/restxq";
import module namespace fw="quodatum:file.walker";
import module namespace ufile = 'vue-poc/file' at "../../lib/file.xqm";
declare namespace c="http://www.w3.org/ns/xproc-step";
(:~ defines accessor functions for each collection type
:)
declare variable $vue-api:MODE:=map{
"webfile":
function($url)
as element(c:directory)
{
let $path := ufile:web( $url)=>trace("vue-api:web ")
return if( file:exists($path))then
fw:directory-list($path,map{"max-depth":1,"include-info":true()})
else
error(xs:QName('vue-api:badpath'),$path)
},
"basexdb":
function($url)
as element(c:directory)
{
<c:directory name="" xml:base="basexdb:/" last-modified="2017-07-01T13:39:38.98691Z" size="4096">{
if($url="/") then
db:list()!
<c:directory name="{db:property(.,'name')}"
last-modified="{db:property(.,'timestamp')}"
size="{db:property(.,'size')}"/>
else
let $map:=vue-api:collection-next($url)
for $name in map:keys($map)
(: db:list-details($db as xs:string, $path as xs:string) as element(resource)* :)
return if($map($name)="file") then
<c:file name="{$name}" size="0"/>
else
<c:directory name="{$name}" size="0"/>
}</c:directory>
}
};
(:~
: history list
@ -59,7 +23,7 @@ function vue-api:history( )
<items type="array">
{$h!(<_ type="object">
<url>{@url/string()}</url>
<mode>{@mode/string()}</mode>
<protocol>{@mode/string()}</protocol>
</_>)}
</items>
</json>
@ -70,18 +34,28 @@ function vue-api:history( )
: Returns folder info.
:)
declare
%rest:path("/vue-poc/api/file")
%rest:path("/vue-poc/api/collection")
%rest:query-param("url", "{$url}")
%rest:query-param("protocol", "{$protocol}")
%rest:query-param("protocol", "{$protocol}","webfile")
%rest:produces("application/json")
%output:method("json")
function vue-api:file($url as xs:string,$protocol as xs:string?)
function vue-api:file($url as xs:string,$protocol as xs:string)
as element(json)
{
let $mode:=if(map:contains($vue-api:MODE,string($protocol)))then $protocol else "webfile"
let $reader:=$vue-api:MODE($mode)
let $items:=$reader($url)
return <json type="object" >
let $reader:=map{
"webfile":ufile:webfile#1,
"basexdb":ufile:basexdb#1
}
let $items:=$reader($protocol)($url)
return vue-api:items($items)
};
declare function vue-api:items($items)
as element(json)
{
<json type="object" >
<folders type="array">
{for $f in $items/c:directory
order by $f/@name/lower-case(.)
@ -99,8 +73,8 @@ as element(json)
}
</files>
</json>
};
};
declare function vue-api:details($f as element(*),$icon as xs:string)
as element(*)*
{
@ -112,23 +86,3 @@ as element(*)*
};
(:~ return map of next level database contents
:@param $url a database base collection e.g /dbname/fred/
:)
declare function vue-api:collection-next($url as xs:string)
as map(*)
{
if(not(starts-with($url,"/")) or ends-with($url,"/")) then
error(xs:QName('vue-api:badpath'),$url)
else
fold-left(
uri-collection($url ),
map{},
function($acc,$this){
let $s:=substring-after($this ,$url || "/")
let $isDir:=contains($s,"/")
let $s:=if($isDir)then substring-before($s,"/") else $s
return map:merge((map:entry($s,if($isDir)then "directory" else "file"),$acc))
}
)
};

View file

@ -3,7 +3,7 @@
<v-container fluid>
<v-card>
<v-toolbar>
<v-toolbar light>
<v-menu bottom right>
<v-btn icon slot="activator"><v-icon >{{icon}}</v-icon></v-btn>
<v-list>
@ -16,7 +16,7 @@
<v-spacer></v-spacer>
<v-text-field prepend-icon="search" label="Filter..." v-model="q" type="search"
hide-details single-line dark @keyup.native.enter="filter"></v-text-field>
hide-details single-line @keyup.native.enter="filter"></v-text-field>
<v-icon>view_module</v-icon>
</v-toolbar>
@ -96,7 +96,7 @@
},
load(url){
this.busy=true
HTTP.get("file",{params:{url:url,protocol:this.protocol}})
HTTP.get("collection",{params:{url:url,protocol:this.protocol}})
.then(r=>{
this.folders=r.data.folders
this.files=r.data.files

View file

@ -4,7 +4,7 @@
<v-list>
<v-list-tile v-for="item in items" v-bind:key="item.title" @click="doEdit(item)" avatar>
<v-list-tile-action>
<v-chip v-text="item.mode">Example Chip</v-chip>
<v-chip v-text="item.protocol">Example Chip</v-chip>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title @click="doEdit(item)" v-text="item.url"></v-list-tile-title>
@ -31,7 +31,7 @@
},
doEdit(item){
console.log("history: ",item)
router.push({ path: 'edit', query: { url:item.url, mode:item.mode }})
router.push({ path: 'edit', query: { url:item.url, protocol:item.protocol }})
}
},
created:function(){

View file

@ -9,5 +9,5 @@
<entry mode="webfile" url='/vue-poc/static/resources/turtle.ttl' />
<entry mode="webfile" url='/vue-poc/static/resources/task.xsd' />
<entry mode="basexdb" url='/abide/abide.xml' />
<entry mode="basexdb" url='/vue-poc' />
</history>

View file

@ -9,7 +9,7 @@
<v-toolbar class="grey lighten-2 black--text">
<v-menu >
<v-btn primary icon dark slot="activator" v-tooltip:top="{ html: path.join('/') }"><v-icon >folder</v-icon></v-btn>
<v-btn primary icon dark slot="activator" v-tooltip:top="{ html: path.join('/') }"><v-icon >{{icon}}</v-icon></v-btn>
<v-list>
<v-list-tile v-for="item in path" :key="item">
<v-list-tile-content @click="showfiles()">
@ -70,14 +70,14 @@
<v-list-tile-avatar>
<v-icon >settings</v-icon>
</v-list-tile-avatar>
<v-list-tile-title >Show settings</v-list-tile-title>
<v-list-tile-title @click="acecmd('showSettingsMenu')" >Show settings</v-list-tile-title>
</v-list-tile>
<v-list-tile @click="acecmd('showKeyboardShortcuts')" avatar>
<v-list-tile-avatar>
<v-icon >keyboard</v-icon>
</v-list-tile-avatar>
<v-list-tile-title >Show keyboard commands</v-list-tile-title>
<v-list-tile-title @click="acecmd('showKeyboardShortcuts')" >Show keyboard commands</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
@ -212,7 +212,8 @@ v-on:annotation="annotation"></vue-ace>
})
},
showfiles(){
router.push({ path: 'files', query: { url: this.path.join("/") }})
var target=this.protocol="basexdb"?"database":"files";
router.push({ path: target, query: { url: this.path.join("/"),protocol:this.protocol }})
},
beautify(){
this.busy=true
@ -251,6 +252,11 @@ v-on:annotation="annotation"></vue-ace>
if(this.dirty)event.preventDefault();
}
},
computed:{
icon(){
return (this.protocol=="basexdb")?"account_balance":"folder"
}
},
created(){
//https://forum.vuejs.org/t/detect-browser-close/5001/3 @fixme
document.addEventListener('beforeunload', this.leaving);

View file

@ -18,22 +18,16 @@ declare namespace c="http://www.w3.org/ns/xproc-step";
declare
%rest:GET %rest:path("/vue-poc/api/edit")
%rest:query-param("url", "{$url}")
%rest:query-param("protocol", "{$protocol}")
%rest:query-param("protocol", "{$protocol}","webfile")
%rest:produces("application/json")
%output:method("json")
function vue-api:edit-get($url as xs:string,$protocol as xs:string)
{
let $path := ufile:web( $url)=>trace("path ")
return if( file:exists($path))then
let $type:=mt:type($path)
let $fetch:=mt:fetch-fn($type("treat-as"))
return <json type="object" >
<url>{$url}</url>
<mimetype>{$type?type}</mimetype>
<data>{$fetch($path)}</data>
</json>
else
error(xs:QName('vue-api:raw'),$path)
let $reader := map{
"webfile":vue-api:get-webfile#1,
"basexdb":vue-api:get-basexdb#1
}
return $reader($protocol)($url)
};
(:~
@ -61,3 +55,39 @@ function vue-api:edit-post($url as xs:string,$data)
error(xs:QName('vue-api:raw'),$path)
};
(:~
: Returns a file content.
:)
declare function vue-api:get-webfile($url as xs:string)
as element(json)
{
let $path := ufile:web( $url)=>trace("path ")
return if( file:exists($path))then
let $type:=mt:type($path)
let $fetch:=mt:fetch-fn($type("treat-as"))
return <json type="object" >
<url>{$url}</url>
<mimetype>{$type?type}</mimetype>
<data>{$fetch($path)}</data>
</json>
else
error(xs:QName('vue-api:raw'),$url)
};
(:~
: Returns a file content.
:)
declare function vue-api:get-basexdb($url as xs:string)
as element(json)
{
if( doc-available($url))then
let $doc:=doc($url)
return <json type="object" >
<url>{$url}</url>
<mimetype>application/xml</mimetype>
<data>{serialize($doc)}</data>
</json>
else
error(xs:QName('vue-api:raw'),$url)
};

View file

@ -1,13 +1,46 @@
<!DOCTYPE html>
<!--
image list ui
-->
<template id="images">
<v-card>
<v-card-actions>
<v-btn @click.native="page+=1">next</v-btn>
{{page}}
<v-btn @click.native="page-=1">back</v-btn>
<v-spacer></v-spacer>
<v-select
v-bind:items="keywords"
v-model="query.keyword"
label="Keyword"
autocomplete
></v-select>
<v-dialog
persistent
v-model="modal"
lazy
full-width
>
<v-text-field
slot="activator"
label="Earliest date"
v-model="query.from"
prepend-icon="event"
readonly
></v-text-field>
<v-date-picker v-model="query.from" scrollable actions>
<template scope="{ save, cancel }">
<v-card-actions>
<v-btn flat primary @click.native="cancel()">Cancel</v-btn>
<v-btn flat primary @click.native="save()">Save</v-btn>
</v-card-actions>
</template>
</v-date-picker>
</v-dialog>
<v-btn @click.native="clear()">Clear</v-btn>
<v-spacer></v-spacer>
<v-btn @click.native="query.page+=1">next</v-btn>
{{query.page}}
<v-btn @click.native="query.page-=1">back</v-btn>
</v-card-actions>
<v-container fluid grid-list-md>
<v-layout row wrap>
@ -16,9 +49,9 @@
v-for="image in images"
:key="image.name"
>
<v-card @click="selected()" class="grey lighten-2 pt-1">
<v-card @click="selected(image)" class="grey lighten-2 pt-1">
<v-card-media :src="src(image)" height="80px" :contain="true"></v-card-media>
<v-card-actions v-tooltip:top="{ html: image.name }">
<v-card-actions v-tooltip:top="{ html: image.id + ' '+image.name }">
<v-btn icon small>
<v-icon>favorite</v-icon>
@ -35,6 +68,16 @@
</v-flex>
</v-layout>
</v-container>
<v-navigation-drawer left light temporary v-model="showInfo">
<v-card>
<v-toolbar class="green white--text">
<v-toolbar-title >{{selitem.name}}</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn flat icon @click.native="showInfo = false"><v-icon>highlight_off</v-icon></v-btn>
</v-toolbar>
<v-card-text> blah blah </v-card-text>
</v-card>
</v-navigation-drawer>
</v-card>
</template>
@ -42,35 +85,56 @@
<script>{
data: () => ({
images:[],
page:0
query:{page:0, // current page
from:null,
keyword:null
},
modal:false, // showing datepicker
keywords:[],
showInfo:false,
selitem:"TODO"
}),
methods:{
src(item){
//return "https://unsplash.it/150/300?image="+Math.floor(Math.random() * 100) + 1
return "data:image/jpeg;base64,"+item.data
},
getImages(){
HTTP.get("images/list?page="+this.page)
HTTP.get("images/list",{params:this.query})
.then(r=>{
this.images=r.data.items
})
})
},
selected(){
alert("not yet")
clear(){
this.query.from=null;
this.query.keyword=null;
},
selected(image){
this.selitem=image;
this.showInfo=true;
}
},
watch:{
page(v){
this.$router.push({ query: { page: this.page }})
"query":{
handler:function(v){
this.$router.push({ query: this.query })
},
deep:true
},
$route(v){
this.getImages()
}
},
created:function(){
this.page=this.$route.query.page || this.page
this.query.page=Number(this.$route.query.page) || this.query.page
this.query.keyword=this.$route.query.keyword || this.query.keyword
this.query.from=this.$route.query.from || this.query.from
this.getImages()
HTTP.get("images/keywords")
.then(r=>{
this.keywords=r.data.items
})
}
}
</script>

View file

@ -4,9 +4,8 @@
: @author Andy Bunce may-2017
:)
module namespace vue-api = 'quodatum:vue.api.images';
import module namespace thb="expkg-zone58:image.thumbnailator";
import module namespace rest = "http://exquery.org/ns/restxq";
import module namespace fw="quodatum:file.walker";
import module namespace entity = 'quodatum.models.generated' at "../../models.gen.xqm";
declare namespace c="http://www.w3.org/ns/xproc-step";
declare variable $vue-api:PICS:="c:\tmp\";
@ -18,33 +17,64 @@ declare
%rest:GET %rest:path("/vue-poc/api/images/list")
%rest:produces("application/json")
%rest:query-param("page", "{$page}",0)
%rest:query-param("from", "{$from}")
%rest:query-param("keyword", "{$keyword}")
%output:method("json")
function vue-api:list( $page as xs:integer)
function vue-api:list( $page as xs:integer,
$from,
$keyword
)
{
let $a:=trace(($from,$keyword),"----------")
let $rowsPerPage:=24
let $opt:=map{"include-filter":".*\.jpg",
"max-depth":-1,
"include-info":true()}
let $d:= fw:directory-list($vue-api:PICS,$opt)
let $f:=$d//c:file/@name/resolve-uri(encode-for-uri(.),base-uri(.))
let $images:=subsequence($f,1+$rowsPerPage*$page,$rowsPerPage)
let $entity:=$entity:list("thumbnail")
let $images:=$entity("data")()
let $images:=if($from)then $images[datetaken ge $from] else $images
let $images:=if($keyword)then $images[keywords/keyword = $keyword] else $images
let $images:=subsequence($images,1+$rowsPerPage*$page,$rowsPerPage)
return <json type="object" >
<items type="array">{
for $f in $images
return <_ type="object">
{vue-api:get-image($f)}
{vue-api:get-image($f,$entity)}
</_>
}</items>
</json>
};
(:~
: keywords
:)
declare
%rest:GET %rest:path("/vue-poc/api/images/keywords")
%rest:produces("application/json")
%output:method("json")
function vue-api:keywords()
{
let $keys:=
collection("/vue-poc/Pictures")/image/keywords/keyword
=>distinct-values()
=>sort("http://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive")
return <json type="object" >
<items type="array">{
$keys!<_ >{.}</_>
}</items>
</json>
};
declare function vue-api:get-image($f as xs:string)
declare function vue-api:get-image($image as element(image),$entity)
as element(*)*
{
<name>{$f}</name>
,<data>{fetch:binary($f)}</data>
,<mime>{fetch:content-type($f)}</mime>
let $id:=$entity?access?id($image)
let $path:=$entity?access?path($image)
let $name:=$entity?access?name($image)
let $thumb:= $vue-api:PICS || $path
let $thumb:=if(file:exists($thumb)) then $thumb else "C:\tmp\art.jpg"
return ( <id>{$id}</id>
,<name>{$name}</name>
,<data>{fetch:binary($thumb)}</data>
,<mime>{fetch:content-type($thumb)}</mime>)
};

View file

@ -0,0 +1,215 @@
(:~
: generate xquery access code for entity definitions
:)
module namespace bf = 'quodatum.tools.buildfields';
declare default function namespace 'quodatum.tools.buildfields';
declare namespace ent="https://github.com/Quodatum/app-doc/entity";
(:~
: write generated xquery module from entity xml
: @param efolder full path to folder with entities e.g. fn:resolve-uri("./data/models")
: @param dest full name of xqm to create e.g. fn:resolve-uri("models.xqm")
:)
declare %updating function write($efolder as xs:string,$dest as xs:string)
{
let $src:=bf:module(bf:sources($efolder))
return file:write-text($dest,$src)
};
(:~
: generate xquery module for given entities as a string
:)
declare function module($entities as element(ent:entity)*) as xs:string
{
let $src:= <text>(: entity access maps
: auto generated from xml files in entities folder at: {fn:current-dateTime()}
:)
module namespace entity = 'quodatum.models.generated';
{bf:build-modules($entities)}
{bf:build-namespaces($entities)}
{( bf:build-describe($entities))}
(:~ map of access functions for entity :)
declare function entity:fields($entity as xs:string)
as map(*){{
$entity:list($entity)("access")
}};
</text>
return $src
};
(:~
: generate xquery for to return field value in the format: "name":function($_){}
:)
declare function accessfn($f as element(ent:field)) as xs:string
{
let $type:=$f/@type/fn:string()
return <field>
"{$f/@name/fn:string()}": function($_ as element()) as {$type} {{$_/{$f/ent:xpath } }}</field>
};
declare function generate($e as element(ent:entity)) as xs:string
{
let $fields:=for $field in $e/ent:fields/ent:field
order by $field/@name
return $field
let $filter:=$e/ent:views/ent:view[@name="filter"]=>fn:tokenize()
let $filter:= $e/ent:fields/ent:field[@name=$filter]/ent:xpath/fn:concat("$item/",.)
return <field>
"{$e/@name/fn:string()}": map{{
"name": "{ $e/@name/fn:string()}",
"description": "{ escape($e/ent:description)}",
"access": map{{ {$fields!accessfn(.)=>fn:string-join(",")} }},
"filter": function($item,$q) as xs:boolean{{
some $e in ( {fn:string-join($filter,", ")}) satisfies
fn:contains($e,$q, 'http://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive')
}},
"json": map{{ {$fields!jsonfn(.)=>fn:string-join(",")} }},
"data": function() as {$e/ent:data/@type/fn:string(.)}*
{{ {let $a:=$e/ent:data/fn:string() return if($a)then $a else "()"} }},
"views": map{{
{$e/ent:views/ent:view!("'" || @name || "': '" ||. || "'")=>fn:string-join(',')}
}}
}}</field>
};
(:~
: @return sequence of element(entity) items for definitions at path
:)
declare function sources($path as xs:string) as element(ent:entity)*
{
let $_:=fn:trace($path,"DD")
let $p:=fn:resolve-uri($path) || "/"
return for $f in file:list($p)
order by $f
return fn:doc(fn:concat($p,$f))/ent:entity
};
(:map for entity :)
declare function build-map($entity as element(ent:entity)) as xs:string
{
let $m:=for $field in $entity/ent:fields/ent:field
order by $field/@name
return accessfn($field)
return <text>
declare variable $entity:{$entity/@name/fn:string()}: map{{ {fn:string-join($m,",")}
}};
</text>
};
(:~
: return xml for suitable json serialization for field
:)
declare function jsonfn($f as element(ent:field))
as xs:string
{
let $name:=$f/@name/fn:string()
let $type:=$f/@type/fn:string()
let $opt:=fn:contains($type,"?")
let $repeat:=fn:contains($type,"*")
let $json-type:=json-type($type)
let $mult:=if($repeat) then "*" else "?"
let $at:=if($json-type ne "string")
then "attribute type {'" || $json-type || "'},"
else ""
(: generate json xml :)
let $simple:=function() as xs:string{
<field>(: {$type} :)
fn:data($_/{$f/ent:xpath })!element {$name} {{ {$at} .}}
</field>
}
let $array:=function() as xs:string{
<field>(: array of strings :)
element {$name} {{
attribute type {{"array"}},
$_/{$f/ent:xpath }!element _ {{ attribute type {{"string"}}, .}}
}}
</field>
}
(: serialize when element :)
let $element:=function() as xs:string{
<field>element {$name} {{
attribute type {{"string"}},
fn:serialize($_/{$f/ent:xpath})}}</field>
}
return <field>
"{$name}": function($_ as element()) as element({$name}){$mult} {{
{if($repeat)then
$array()
else if($type="element()") then
$element()
else $simple()} }}</field>
};
(:~ convert xs type to json
:)
declare function json-type($xsd as xs:string) as xs:string{
switch ($xsd)
case "element()" return "string"
case "xs:boolean" return "boolean"
case "xs:integer" return "number"
case "xs:float" return "number"
case "xs:double" return "number"
case "xs:string*" return "array"
default return "string"
};
(:~ declare any namespaces found :)
declare function build-namespaces($entities as element()*){
for $n in distinct-deep($entities/ent:namespace)
return
<text>declare namespace {$n/@prefix/fn:string()}='{$n/@uri/fn:string()}';
</text>
};
(:~ declare any namespaces found :)
declare function build-modules($entities as element()*){
for $n in distinct-deep($entities/ent:module)
return
<text>import module namespace {$n/@prefix/fn:string()}='{$n/@namespace/fn:string()}';
</text>
};
declare function build-describe($entities){
let $m:=for $e in $entities
return generate($e)
return <text>
declare variable $entity:list:=map {{ {fn:string-join($m,",")}
}};
</text>
};
declare function escape($str as xs:string)
as xs:string{
fn:replace(
fn:replace($str,'"','""'),
"'","''")
};
(:-----from functx-------------------:)
declare function distinct-deep
( $nodes as node()* ) as node()* {
for $seq in (1 to fn:count($nodes))
return $nodes[$seq][fn:not(is-node-in-sequence-deep-equal(
.,$nodes[fn:position() < $seq]))]
};
declare function is-node-in-sequence-deep-equal
( $node as node()? ,
$seq as node()* ) as xs:boolean {
some $nodeInSeq in $seq satisfies fn:deep-equal($nodeInSeq,$node)
} ;

View file

@ -0,0 +1,79 @@
<!DOCTYPE html>
<template id="model">
<v-container fluid>
<v-card >
<v-card-title class="blue accent-4">
<span class="white--text">Generate <code>model.gen.xqm</code></span>
</v-card-title>
<v-card-text>
<v-container fluid>
<v-layout row wrap>
<v-flex xs6>
<v-text-field v-model="params.efolder"
label="Folder containing model definitions as xml"
></v-text-field>
</v-flex>
<v-flex xs6>
<v-text-field v-model="params.target"
label="Path to xqm file to generate"
></v-text-field>
</v-flex>
<v-flex xs12>
<code>{{code}}</code>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-btn primary @click.native="submit()" :loading="waiting"
:disabled="waiting">
<v-icon>play_circle_outline</v-icon>
Run</v-btn>
</v-card-actions>
<v-snackbar v-model="snackbar.show"
:timeout="6000"
:success="snackbar.context === 'success'"
:error="snackbar.context === 'error'"
>
{{ snackbar.msg }}
<v-btn dark flat @click.native="snackbar.show = false">Close</v-btn>
</v-snackbar>
</v-card>
</v-container>
</template>
<script>{
data: function(){
return {
params:{
efolder:"C:/Users/andy/git/vue-poc/src/vue-poc/models",
target:"C:/Users/andy/git/vue-poc/src/vue-poc/models.gen.xqm"
},
waiting:false,
snackbar:{show:false,msg:"",context:"success"},
}
},
methods:{
submit(){
this.waiting=true
HTTP.post("tasks/model",Qs.stringify(this.params))
.then(r=>{
this.waiting=false
this.snackbar={show:true,msg:r.data.msg,context:"success"}
console.log(r.data)
})
.catch(error=>{
this.waiting=false
this.snackbar={show:true,msg:"Problem",context:"error"}
console.log(error);
});
}
},
computed:{
code(){return 'import module namespace entity = "quodatum.models.generated" at "'+this.params.target+'";'}
}
}
</script>

View file

@ -0,0 +1,27 @@
(:~
: Update `generated/models.xqm` from files in `data/models`
: using file:///C:/Users/andy/workspace/app-doc/src/doc/data/doc/models
: $efolder:="file:///C:/Users/andy/workspace/app-doc/src/doc/data/doc/models"
: $target:="file:///C:/Users/andy/workspace/app-doc/src/doc/generated/models.xqm"
:)
module namespace vue-api = 'quodatum:vue.api';
import module namespace bf = 'quodatum.tools.buildfields' at "entity-gen.xqm";
(:~
: Returns a file content.
:)
declare
%rest:POST %rest:path("/vue-poc/api/tasks/model")
%rest:form-param("efolder", "{$efolder}")
%rest:form-param("target", "{$target}")
%rest:produces("application/json")
%output:method("json")
%updating
function vue-api:model($efolder ,$target )
{(
prof:variables(),
bf:write($efolder,$target),
db:output(<json type="object"><msg>Updated: {$target}</msg></json>)
)};

View file

@ -2,6 +2,7 @@
<template id="task">
<v-container fluid>
<h1>Tasks</h1>
<router-link to="tasks/model">model</router-link>
</v-container>
</template>

View file

@ -0,0 +1,215 @@
(:~
: generate xquery access code for entity definitions
:)
module namespace bf = 'quodatum.tools.buildfields';
declare default function namespace 'quodatum.tools.buildfields';
declare namespace ent="https://github.com/Quodatum/app-doc/entity";
(:~
: write generated xquery module from entity xml
: @param efolder full path to folder with entities e.g. fn:resolve-uri("./data/models")
: @param dest full name of xqm to create e.g. fn:resolve-uri("models.xqm")
:)
declare %updating function write($efolder as xs:string,$dest as xs:string)
{
let $src:=bf:module(bf:sources($efolder))
return file:write-text($dest,$src)
};
(:~
: generate xquery module for given entities as a string
:)
declare function module($entities as element(ent:entity)*) as xs:string
{
let $src:= <text>(: entity access maps
: auto generated from xml files in entities folder at: {fn:current-dateTime()}
:)
module namespace entity = 'quodatum.models.generated';
{bf:build-modules($entities)}
{bf:build-namespaces($entities)}
{( bf:build-describe($entities))}
(:~ map of access functions for entity :)
declare function entity:fields($entity as xs:string)
as map(*){{
$entity:list($entity)("access")
}};
</text>
return $src
};
(:~
: generate xquery for to return field value in the format: "name":function($_){}
:)
declare function accessfn($f as element(ent:field)) as xs:string
{
let $type:=$f/@type/fn:string()
return <field>
"{$f/@name/fn:string()}": function($_ as element()) as {$type} {{$_/{$f/ent:xpath } }}</field>
};
declare function generate($e as element(ent:entity)) as xs:string
{
let $fields:=for $field in $e/ent:fields/ent:field
order by $field/@name
return $field
let $filter:=$e/ent:views/ent:view[@name="filter"]=>fn:tokenize()
let $filter:= $e/ent:fields/ent:field[@name=$filter]/ent:xpath/fn:concat("$item/",.)
return <field>
"{$e/@name/fn:string()}": map{{
"name": "{ $e/@name/fn:string()}",
"description": "{ escape($e/ent:description)}",
"access": map{{ {$fields!accessfn(.)=>fn:string-join(",")} }},
"filter": function($item,$q) as xs:boolean{{
some $e in ( {fn:string-join($filter,", ")}) satisfies
fn:contains($e,$q, 'http://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive')
}},
"json": map{{ {$fields!jsonfn(.)=>fn:string-join(",")} }},
"data": function() as {$e/ent:data/@type/fn:string(.)}*
{{ {let $a:=$e/ent:data/fn:string() return if($a)then $a else "()"} }},
"views": map{{
{$e/ent:views/ent:view!("'" || @name || "': '" ||. || "'")=>fn:string-join(',')}
}}
}}</field>
};
(:~
: @return sequence of element(entity) items for definitions at path
:)
declare function sources($path as xs:string) as element(ent:entity)*
{
let $_:=fn:trace($path,"DD")
let $p:=fn:resolve-uri($path) || "/"
return for $f in file:list($p)
order by $f
return fn:doc(fn:concat($p,$f))/ent:entity
};
(:map for entity :)
declare function build-map($entity as element(ent:entity)) as xs:string
{
let $m:=for $field in $entity/ent:fields/ent:field
order by $field/@name
return accessfn($field)
return <text>
declare variable $entity:{$entity/@name/fn:string()}: map{{ {fn:string-join($m,",")}
}};
</text>
};
(:~
: return xml for suitable json serialization for field
:)
declare function jsonfn($f as element(ent:field))
as xs:string
{
let $name:=$f/@name/fn:string()
let $type:=$f/@type/fn:string()
let $opt:=fn:contains($type,"?")
let $repeat:=fn:contains($type,"*")
let $json-type:=json-type($type)
let $mult:=if($repeat) then "*" else "?"
let $at:=if($json-type ne "string")
then "attribute type {'" || $json-type || "'},"
else ""
(: generate json xml :)
let $simple:=function() as xs:string{
<field>(: {$type} :)
fn:data($_/{$f/ent:xpath })!element {$name} {{ {$at} .}}
</field>
}
let $array:=function() as xs:string{
<field>(: array of strings :)
element {$name} {{
attribute type {{"array"}},
$_/{$f/ent:xpath }!element _ {{ attribute type {{"string"}}, .}}
}}
</field>
}
(: serialize when element :)
let $element:=function() as xs:string{
<field>element {$name} {{
attribute type {{"string"}},
fn:serialize($_/{$f/ent:xpath})}}</field>
}
return <field>
"{$name}": function($_ as element()) as element({$name}){$mult} {{
{if($repeat)then
$array()
else if($type="element()") then
$element()
else $simple()} }}</field>
};
(:~ convert xs type to json
:)
declare function json-type($xsd as xs:string) as xs:string{
switch ($xsd)
case "element()" return "string"
case "xs:boolean" return "boolean"
case "xs:integer" return "number"
case "xs:float" return "number"
case "xs:double" return "number"
case "xs:string*" return "array"
default return "string"
};
(:~ declare any namespaces found :)
declare function build-namespaces($entities as element()*){
for $n in distinct-deep($entities/ent:namespace)
return
<text>declare namespace {$n/@prefix/fn:string()}='{$n/@uri/fn:string()}';
</text>
};
(:~ declare any namespaces found :)
declare function build-modules($entities as element()*){
for $n in distinct-deep($entities/ent:module)
return
<text>import module namespace {$n/@prefix/fn:string()}='{$n/@namespace/fn:string()}';
</text>
};
declare function build-describe($entities){
let $m:=for $e in $entities
return generate($e)
return <text>
declare variable $entity:list:=map {{ {fn:string-join($m,",")}
}};
</text>
};
declare function escape($str as xs:string)
as xs:string{
fn:replace(
fn:replace($str,'"','""'),
"'","''")
};
(:-----from functx-------------------:)
declare function distinct-deep
( $nodes as node()* ) as node()* {
for $seq in (1 to fn:count($nodes))
return $nodes[$seq][fn:not(is-node-in-sequence-deep-equal(
.,$nodes[fn:position() < $seq]))]
};
declare function is-node-in-sequence-deep-equal
( $node as node()? ,
$seq as node()* ) as xs:boolean {
some $nodeInSeq in $seq satisfies fn:deep-equal($nodeInSeq,$node)
} ;

View file

@ -4,7 +4,8 @@
: @author Andy Bunce, 2017
:)
module namespace ufile = 'vue-poc/file';
import module namespace fw="quodatum:file.walker";
declare namespace c="http://www.w3.org/ns/xproc-step";
(:~
: resolve path relative to basex webpath
: file("/fred")=>C:\Program Files (x86)\BaseX\webapp\fred
@ -21,3 +22,54 @@ as xs:string
return file:resolve-path($file,$webroot)
};
declare function ufile:webfile($url as xs:string)
as element(c:directory)
{
let $path := ufile:web( $url)=>trace("vue-api:web ")
return if( file:exists($path))then
fw:directory-list($path,map{"max-depth":1,"include-info":true()})
else
error(xs:QName('ufile:badpath'),$path)
};
declare function ufile:basexdb($url as xs:string)
as element(c:directory)
{
<c:directory name="" xml:base="basexdb:/" last-modified="2017-07-01T13:39:38.98691Z" size="4096">{
if($url="/") then
db:list()!
<c:directory name="{db:property(.,'name')}"
last-modified="{db:property(.,'timestamp')}"
size="{db:property(.,'size')}"/>
else
let $map:=ufile:collection-next($url)
for $name in map:keys($map)
(: db:list-details($db as xs:string, $path as xs:string) as element(resource)* :)
return if($map($name)="file") then
<c:file name="{$name}" size="0"/>
else
<c:directory name="{$name}" size="0"/>
}</c:directory>
};
(:~ return map of next level database contents
:@param $url a database base collection e.g /dbname/fred/
:)
declare function ufile:collection-next($url as xs:string)
as map(*)
{
if(not(starts-with($url,"/")) or ends-with($url,"/")) then
error(xs:QName('ufile:badcollection'),$url)
else
fold-left(
uri-collection($url ),
map{},
function($acc,$this){
let $s:=substring-after($this ,$url || "/")
let $isDir:=contains($s,"/")
let $s:=if($isDir)then substring-before($s,"/") else $s
return map:merge((map:entry($s,if($isDir)then "directory" else "file"),$acc))
}
)
};

View file

@ -0,0 +1,56 @@
(: entity access maps
: auto generated from xml files in entities folder at: 2017-07-17T17:47:26.588+01:00
:)
module namespace entity = 'quodatum.models.generated';
declare namespace c='http://www.w3.org/ns/xproc-step';
declare variable $entity:list:=map {
"thumbnail": map{
"name": "thumbnail",
"description": "an image.",
"access": map{
"id": function($_ as element()) as xs:integer {$_/db:node-id(.) },
"name": function($_ as element()) as xs:string {$_/file/@name },
"path": function($_ as element()) as xs:string {$_/file/@path },
"size": function($_ as element()) as xs:integer {$_/0 } },
"filter": function($item,$q) as xs:boolean{
some $e in ( $item/file/@name) satisfies
fn:contains($e,$q, 'http://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive')
},
"json": map{
"id": function($_ as element()) as element(id)? {
(: xs:integer :)
fn:data($_/db:node-id(.))!element id { attribute type {'number'}, .}
},
"name": function($_ as element()) as element(name)? {
(: xs:string :)
fn:data($_/file/@name)!element name { .}
},
"path": function($_ as element()) as element(path)? {
(: xs:string :)
fn:data($_/file/@path)!element path { .}
},
"size": function($_ as element()) as element(size)? {
(: xs:integer :)
fn:data($_/0)!element size { attribute type {'number'}, .}
} },
"data": function() as element(image)*
{ collection("/vue-poc/Pictures")/image },
"views": map{
'filter': 'name'
}
}
};
(:~ map of access functions for entity :)
declare function entity:fields($entity as xs:string)
as map(*){
$entity:list($entity)("access")
};

View file

@ -1,15 +1,22 @@
<entity name="thumbnail" xmlns="https://github.com/Quodatum/app-doc/entity">
<description>an image.</description>
<namespace prefix="c" uri="http://www.w3.org/ns/xproc-step"/>
<module prefix="cmpx" namespace="quodatum.cmpx" />
<fields>
<field name="id" type="xs:integer">
<description>Database Id.</description>
<xpath>db:node-id(.)</xpath>
</field>
<field name="name" type="xs:string">
<description>File name.</description>
<xpath>@name</xpath>
<xpath>file/@name</xpath>
</field>
<field name="path" type="xs:string">
<description>File path. e.g. Pictures/2012/2012-06-23/skating.jpg</description>
<xpath>file/@path</xpath>
</field>
<field name="size" type="xs:integer">
<description>file size.</description>
<xpath>@size</xpath>
<xpath>0</xpath>
</field>
</fields>
@ -17,6 +24,6 @@
<view name="filter">name</view>
</views>
<iconclass>fa fa-file</iconclass>
<data type="element(*)">doc("/vue-poc/pics.xml")//c:file</data>
<data type="element(image)">collection("/vue-poc/Pictures")/image</data>
</entity>

View file

@ -1,4 +1,4 @@
// generated 2017-07-12T11:37:10.722+01:00
// generated 2017-07-17T17:49:54.864+01:00
Vue.component('my-component',{template:`
<a :href="href" :target="href"> {{href}}<v-icon>link</v-icon></a>
`,
@ -36,9 +36,7 @@
<v-list-tile-action>
<v-icon>{{ item.model ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
</v-list-tile-content>
</v-list-tile>
<template v-for="(child, i) in item.children">
<v-list-tile :to="child.href" :key="i" ripple="">
@ -157,7 +155,7 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
<v-container fluid="">
<v-card>
<v-toolbar>
<v-toolbar light="">
<v-menu bottom="" right="">
<v-btn icon="" slot="activator"><v-icon>{{icon}}</v-icon></v-btn>
<v-list>
@ -169,7 +167,7 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
<v-toolbar-title>{{ url }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-text-field prepend-icon="search" label="Filter..." v-model="q" type="search" hide-details="" single-line="" dark="" @keyup.native.enter="filter"></v-text-field>
<v-text-field prepend-icon="search" label="Filter..." v-model="q" type="search" hide-details="" single-line="" @keyup.native.enter="filter"></v-text-field>
<v-icon>view_module</v-icon>
</v-toolbar>
@ -248,7 +246,7 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
},
load(url){
this.busy=true
HTTP.get("file",{params:{url:url,protocol:this.protocol}})
HTTP.get("collection",{params:{url:url,protocol:this.protocol}})
.then(r=>{
this.folders=r.data.folders
this.files=r.data.files
@ -311,7 +309,7 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
<v-list>
<v-list-tile v-for="item in items" v-bind:key="item.title" @click="doEdit(item)" avatar="">
<v-list-tile-action>
<v-chip v-text="item.mode">Example Chip</v-chip>
<v-chip v-text="item.protocol">Example Chip</v-chip>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title @click="doEdit(item)" v-text="item.url"></v-list-tile-title>
@ -337,7 +335,7 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
},
doEdit(item){
console.log("history: ",item)
router.push({ path: 'edit', query: { url:item.url, mode:item.mode }})
router.push({ path: 'edit', query: { url:item.url, protocol:item.protocol }})
}
},
created:function(){
@ -357,7 +355,7 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
<v-toolbar class="grey lighten-2 black--text">
<v-menu>
<v-btn primary="" icon="" dark="" slot="activator" v-tooltip:top="{ html: path.join('/') }"><v-icon>folder</v-icon></v-btn>
<v-btn primary="" icon="" dark="" slot="activator" v-tooltip:top="{ html: path.join('/') }"><v-icon>{{icon}}</v-icon></v-btn>
<v-list>
<v-list-tile v-for="item in path" :key="item">
<v-list-tile-content @click="showfiles()">
@ -416,14 +414,14 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
<v-list-tile-avatar>
<v-icon>settings</v-icon>
</v-list-tile-avatar>
<v-list-tile-title>Show settings</v-list-tile-title>
<v-list-tile-title @click="acecmd('showSettingsMenu')">Show settings</v-list-tile-title>
</v-list-tile>
<v-list-tile @click="acecmd('showKeyboardShortcuts')" avatar="">
<v-list-tile-avatar>
<v-icon>keyboard</v-icon>
</v-list-tile-avatar>
<v-list-tile-title>Show keyboard commands</v-list-tile-title>
<v-list-tile-title @click="acecmd('showKeyboardShortcuts')">Show keyboard commands</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
@ -555,7 +553,8 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
})
},
showfiles(){
router.push({ path: 'files', query: { url: this.path.join("/") }})
var target=this.protocol="basexdb"?"database":"files";
router.push({ path: target, query: { url: this.path.join("/"),protocol:this.protocol }})
},
beautify(){
this.busy=true
@ -594,6 +593,11 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
if(this.dirty)event.preventDefault();
}
},
computed:{
icon(){
return (this.protocol=="basexdb")?"account_balance":"folder"
}
},
created(){
//https://forum.vuejs.org/t/detect-browser-close/5001/3 @fixme
document.addEventListener('beforeunload', this.leaving);
@ -788,18 +792,31 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
<v-card>
<v-card-actions>
<v-btn @click.native="page+=1">next</v-btn>
{{page}}
<v-btn @click.native="page-=1">back</v-btn>
<v-spacer></v-spacer>
<v-select v-bind:items="keywords" v-model="query.keyword" label="Keyword" autocomplete=""></v-select>
<v-dialog persistent="" v-model="modal" lazy="" full-width="">
<v-text-field slot="activator" label="Earliest date" v-model="query.from" prepend-icon="event" readonly=""></v-text-field>
<v-date-picker v-model="query.from" scrollable="" actions="">
<template scope="{ save, cancel }">
<v-card-actions>
<v-btn flat="" primary="" @click.native="cancel()">Cancel</v-btn>
<v-btn flat="" primary="" @click.native="save()">Save</v-btn>
</v-card-actions>
</template>
</v-date-picker>
</v-dialog>
<v-btn @click.native="clear()">Clear</v-btn>
<v-spacer></v-spacer>
<v-btn @click.native="query.page+=1">next</v-btn>
{{query.page}}
<v-btn @click.native="query.page-=1">back</v-btn>
</v-card-actions>
<v-container fluid="" grid-list-md="">
<v-layout row="" wrap="">
<v-flex height="80px" xs2="" v-for="image in images" :key="image.name">
<v-card @click="selected()" class="grey lighten-2 pt-1">
<v-card @click="selected(image)" class="grey lighten-2 pt-1">
<v-card-media :src="src(image)" height="80px" :contain="true"></v-card-media>
<v-card-actions v-tooltip:top="{ html: image.name }">
<v-card-actions v-tooltip:top="{ html: image.id + ' '+image.name }">
<v-btn icon="" small="">
<v-icon>favorite</v-icon>
@ -816,41 +833,72 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
</v-flex>
</v-layout>
</v-container>
<v-navigation-drawer left="" light="" temporary="" v-model="showInfo">
<v-card>
<v-toolbar class="green white--text">
<v-toolbar-title>{{selitem.name}}</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn flat="" icon="" @click.native="showInfo = false"><v-icon>highlight_off</v-icon></v-btn>
</v-toolbar>
<v-card-text> blah blah </v-card-text>
</v-card>
</v-navigation-drawer>
</v-card>
`,
data: () => ({
images:[],
page:0
query:{page:0, // current page
from:null,
keyword:null
},
modal:false, // showing datepicker
keywords:[],
showInfo:false,
selitem:"TODO"
}),
methods:{
src(item){
//return "https://unsplash.it/150/300?image="+Math.floor(Math.random() * 100) + 1
return "data:image/jpeg;base64,"+item.data
},
getImages(){
HTTP.get("images/list?page="+this.page)
HTTP.get("images/list",{params:this.query})
.then(r=>{
this.images=r.data.items
})
})
},
selected(){
alert("not yet")
clear(){
this.query.from=null;
this.query.keyword=null;
},
selected(image){
this.selitem=image;
this.showInfo=true;
}
},
watch:{
page(v){
this.$router.push({ query: { page: this.page }})
"query":{
handler:function(v){
this.$router.push({ query: this.query })
},
deep:true
},
$route(v){
this.getImages()
}
},
created:function(){
this.page=this.$route.query.page || this.page
this.query.page=Number(this.$route.query.page) || this.query.page
this.query.keyword=this.$route.query.keyword || this.query.keyword
this.query.from=this.$route.query.from || this.query.from
this.getImages()
HTTP.get("images/keywords")
.then(r=>{
this.keywords=r.data.items
})
}
}
@ -1015,6 +1063,75 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
}
}
);
const Model=Vue.extend({template:`
<v-container fluid="">
<v-card>
<v-card-title class="blue accent-4">
<span class="white--text">Generate <code>model.gen.xqm</code></span>
</v-card-title>
<v-card-text>
<v-container fluid="">
<v-layout row="" wrap="">
<v-flex xs6="">
<v-text-field v-model="params.efolder" label="Folder containing model definitions as xml"></v-text-field>
</v-flex>
<v-flex xs6="">
<v-text-field v-model="params.target" label="Path to xqm file to generate"></v-text-field>
</v-flex>
<v-flex xs12="">
<code>{{code}}</code>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-btn primary="" @click.native="submit()" :loading="waiting" :disabled="waiting">
<v-icon>play_circle_outline</v-icon>
Run</v-btn>
</v-card-actions>
<v-snackbar v-model="snackbar.show" :timeout="6000" :success="snackbar.context === 'success'" :error="snackbar.context === 'error'">
{{ snackbar.msg }}
<v-btn dark="" flat="" @click.native="snackbar.show = false">Close</v-btn>
</v-snackbar>
</v-card>
</v-container>
`,
data: function(){
return {
params:{
efolder:"C:/Users/andy/git/vue-poc/src/vue-poc/models",
target:"C:/Users/andy/git/vue-poc/src/vue-poc/models.gen.xqm"
},
waiting:false,
snackbar:{show:false,msg:"",context:"success"},
}
},
methods:{
submit(){
this.waiting=true
HTTP.post("tasks/model",Qs.stringify(this.params))
.then(r=>{
this.waiting=false
this.snackbar={show:true,msg:r.data.msg,context:"success"}
console.log(r.data)
})
.catch(error=>{
this.waiting=false
this.snackbar={show:true,msg:"Problem",context:"error"}
console.log(error);
});
}
},
computed:{
code(){return 'import module namespace entity = "quodatum.models.generated" at "'+this.params.target+'";'}
}
}
);
const People=Vue.extend({template:`
<v-container fluid="">
@ -1493,6 +1610,7 @@ v0.0.2 </v-card-title> </v-card> </v-flex> <v-flex xs4="">
const Task=Vue.extend({template:`
<v-container fluid="">
<h1>Tasks</h1>
<router-link to="tasks/model">model</router-link>
</v-container>
`,
@ -1633,6 +1751,7 @@ const router = new VueRouter({
{ path: '/eval', component: Eval,meta:{title:"Evaluate XQuery"} },
{ path: '/logs', component: Log,meta:{title:"Server logs"} },
{ path: '/tasks', component: Task,meta:{title:"Runnable tasks"} },
{ path: '/tasks/model', component: Model,meta:{title:"build model"} },
{ path: '/jobs', component: Job,meta:{title:"Jobs"} },
{ path: '*', component: Notfound,meta:{title:"Page not found"} }
],
@ -1673,42 +1792,42 @@ const app = new Vue({
text: 'Collections' ,
model: false,
children: [
{href: 'database', text: 'Databases',icon: 'account_balance' },
{href: 'files', text: 'File system',icon: 'folder' },
{href: 'edit',text: 'edit',icon: 'mode_edit'},
{href: 'history',text: 'history',icon: 'history'},
{href: 'logs',text: 'Server logs',icon: 'dns'}
{href: '/database', text: 'Databases',icon: 'account_balance' },
{href: '/files', text: 'File system',icon: 'folder' },
{href: '/edit',text: 'edit',icon: 'mode_edit'},
{href: '/history',text: 'history',icon: 'history'},
{href: '/logs',text: 'Server logs',icon: 'dns'}
]},
{
icon: 'directions_run',
text: 'Actions' ,
model: false,
children: [
{href: 'eval',text: 'Query',icon: 'play_circle_outline'},
{href: 'jobs',text: 'Running jobs',icon: 'dashboard'},
{href: 'tasks',text: 'Tasks',icon: 'history'},
{href: '/eval',text: 'Query',icon: 'play_circle_outline'},
{href: '/jobs',text: 'Running jobs',icon: 'dashboard'},
{href: '/tasks',text: 'Tasks',icon: 'history'}
]},
{
icon: 'more_horiz',
text: 'More' ,
model: false,
children: [
{href: 'people',text: 'People',icon: 'person'},
{href: 'select',text: 'select',icon: 'extension'},
{href: 'puzzle',text: 'Puzzle',icon: 'extension'},
{href: 'images',text: 'Images',icon: 'camera_roll'},
{href: 'tabs',text: 'tabs',icon: 'switch_camera'},
{href: 'ping',text: 'ping',icon: 'update'},
{href: 'thumbnail',text: 'thumbnail',icon: 'touch_app'}
{href: '/people',text: 'People',icon: 'person'},
{href: '/select',text: 'select',icon: 'extension'},
{href: '/puzzle',text: 'Puzzle',icon: 'extension'},
{href: '/images',text: 'Images',icon: 'camera_roll'},
{href: '/tabs',text: 'tabs',icon: 'switch_camera'},
{href: '/ping',text: 'ping',icon: 'update'},
{href: '/thumbnail',text: 'thumbnail',icon: 'touch_app'}
]},
{href: 'settings',text: 'settings',icon: 'settings' }
{href: '/settings',text: 'settings',icon: 'settings' }
]
}},
methods: {
search(){
this.$router.push({path: 'search',query: { q: this.q }})
this.$router.push({path: '/search',query: { q: this.q }})
},
logout(){
HTTP.get("logout").then(r=>{

View file

@ -9,17 +9,17 @@
<title>Vue Router Test</title>
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic">
<link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons">
<link href="https://unpkg.com/vuetify@0.13.1/dist/vuetify.min.css" rel="stylesheet" type="text/css">
<link href="https://unpkg.com/vuetify@0.14.2/dist/vuetify.min.css" rel="stylesheet" type="text/css">
<link href="https://unpkg.com/vue-multiselect@2.0.0-beta.15/dist/vue-multiselect.min.css" rel="stylesheet" type="text/css">
<link href="/vue-poc/ui/app.css" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="/vue-poc/ui/icon.png"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/2.5.3/vue-router.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.1/axios.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.4.0/qs.js"></script>
<script src="https://unpkg.com/vuetify@0.13.1/dist/vuetify.min.js"></script>
<script src="https://unpkg.com/vuetify@0.14.2/dist/vuetify.min.js"></script>
<script src="https://unpkg.com/vue-multiselect@2.0.0-beta.15/dist/vue-multiselect.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.7/ace.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.7/ext-language_tools.js"></script>

View file

@ -0,0 +1,15 @@
(:~ generate image docs from meta docs 52 sec :)
import module namespace metadata = 'expkg-zone58:image.metadata';
for $meta in collection("/vue-poc/Pictures")/metadata
let $loc:=db:path($meta)=>tokenize("/")
let $name:=$loc[count($loc)-1]
let $path:= subsequence($loc,1,count($loc)-1)=>string-join("/")
let $image:=<image>
<file name="{$name}" path="{$path}"/>{
metadata:core($meta),
metadata:geo($meta),
metadata:keywords($meta)
} </image>
let $target:=$path || "/image.xml"
return db:replace("vue-poc",$target,$image)

View file

@ -0,0 +1,12 @@
(:~
: Update `generated/models.xqm` from files in `data/models`
: using file:///C:/Users/andy/workspace/app-doc/src/doc/data/doc/models
:)
declare namespace task="https://github.com/Quodatum/app-doc/task";
import module namespace bf = 'quodatum.tools.buildfields' at "../lib/entity-gen.xqm";
let $efolder:="file:///C:/Users/andy/workspace/app-doc/src/doc/data/doc/models"
let $target:="file:///C:/Users/andy/workspace/app-doc/src/doc/generated/models.xqm"
return (bf:write($efolder,$target),db:output("generated C:/Users/andy/workspace/app-doc/src/doc/generated/models.xqm"))

View file

@ -1,5 +1,5 @@
(:~
: create app.js from vue files
: create app-gen.js from vue files
:)
import module namespace html5="text.html5" at "html5parse.xqm";
import module namespace fw="quodatum:file.walker";