Results display properly

This commit is contained in:
PMKuipers 2024-02-23 09:20:10 +01:00
parent f8b18d4d6e
commit b3af1fa8c4
27 changed files with 1303 additions and 262 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
amd/build/util/css-calc.min.js vendored Normal file
View file

@ -0,0 +1,3 @@
define("local_treestudyplan/util/css-calc",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.Units=_exports.UnitRegexpStr=_exports.UnitRegexpGM=_exports.UnitRegexp=_exports.Relative=_exports.Absolute=void 0,_exports.calc=calc,_exports.calcCtx=calcCtx,_exports.convert=convert,_exports.convertAllInStr=convertAllInStr;const Absolute={px:1,cm:96/2.54,mm:96/25.4,Q:96/101.6,in:96,pc:16,pt:96/72};_exports.Absolute=Absolute;const Relative={vh:function(){let count=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,ctx=arguments.length>1?arguments[1]:void 0;return(ctx?ctx.viewportHeight:window.innerHeight)/100*count},vw:function(){let count=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,ctx=arguments.length>1?arguments[1]:void 0;return(ctx?ctx.viewportWidth:window.innerWidth)/100*count},vmin:function(){let count=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,ctx=arguments.length>1?arguments[1]:void 0;return(ctx?Math.min(ctx.viewportWidth,ctx.viewportHeight):Math.min(window.innerWidth,window.innerHeight))/100*count},vmax:function(){let count=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,ctx=arguments.length>1?arguments[1]:void 0;return(ctx?Math.max(ctx.viewportWidth,ctx.viewportHeight):Math.max(window.innerWidth,window.innerHeight))/100*count},rem:function(){let count=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,ctx=arguments.length>1?arguments[1]:void 0;return(ctx?ctx.htmlFontSize:parseFloat(window.getComputedStyle(document.querySelector("html")).fontSize))*count},"%w":function(){let count=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,ctx=arguments.length>1?arguments[1]:void 0;return(ctx?ctx.width:document.body.clientWidth)/100*count},"%h":function(){let count=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,ctx=arguments.length>1?arguments[1]:void 0;return(ctx?ctx.height:document.body.clientHeight)/100*count}};_exports.Relative=Relative;const Units={...Relative,...Absolute};_exports.Units=Units;const UnitRegexpStr=`(?:\\s|^)(\\d*(?:\\.\\d+)?)(${Object.keys(Units).join("|")})(?:\\s|$|\\n)`;_exports.UnitRegexpStr=UnitRegexpStr;const UnitRegexp=new RegExp(UnitRegexpStr);_exports.UnitRegexp=UnitRegexp;const UnitRegexpGM=new RegExp(UnitRegexpStr,"gm");function convert(count,fromUnits,toUnits){let ctx=arguments.length>3&&void 0!==arguments[3]?arguments[3]:calcCtx();const baseUnit=Units[fromUnits],basePx="function"==typeof baseUnit?baseUnit(count,ctx):baseUnit*count,dstUnit=Units[toUnits];return basePx/("function"==typeof dstUnit?dstUnit(1,ctx):dstUnit)}function convertAllInStr(expr,toUnits){let ctx=arguments.length>2&&void 0!==arguments[2]?arguments[2]:calcCtx();return expr.replace(UnitRegexpGM,((substr,count,unit)=>convert(parseFloat(count),unit,toUnits,ctx).toString()))}function calcCtx(el){if(el){const rect=el.getBoundingClientRect();return{width:rect.width,height:rect.height,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,htmlFontSize:parseFloat(window.getComputedStyle(document.querySelector("html")).fontSize)}}return{width:document.body.clientWidth,height:document.body.clientHeight,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,htmlFontSize:parseFloat(window.getComputedStyle(document.querySelector("html")).fontSize)}}function calc(expression,el_ctx,ctx){return void 0===el_ctx?ctx=calcCtx():el_ctx instanceof HTMLElement?ctx||(ctx=calcCtx(el_ctx)):ctx=el_ctx,eval(convertAllInStr(expression,"px",ctx))}_exports.UnitRegexpGM=UnitRegexpGM}));
//# sourceMappingURL=css-calc.min.js.map

File diff suppressed because one or more lines are too long

3
amd/build/util/fittext-vue.min.js vendored Normal file
View file

@ -0,0 +1,3 @@
define("local_treestudyplan/util/fittext-vue",["exports","./css-calc","./fitty","./textfit"],(function(_exports,_cssCalc,_fitty,_textfit){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_fitty=(obj=_fitty)&&obj.__esModule?obj:{default:obj};var _default={install(Vue){Vue.component("fittext",{props:{maxsize:{type:String,default:"512px"},minsize:{type:String,default:"10px"},vertical:Boolean,singleline:Boolean,dynamic:Boolean},data:()=>({resizeObserver:null,mutationObserver:null}),computed:{rootStyle(){return this.vertical?"height: 100%;":"width: 100%;"}},methods:{},mounted(){(0,_fitty.default)(this.$refs.text,{minSize:(0,_cssCalc.calc)(this.minsize),maxSize:(0,_cssCalc.calc)(this.maxsize),vertical:this.vertical,multiline:!this.singleline})},unmounted(){this.mutationObserver&&this.mutationObserver.disconnect(),this.resizeObserver&&this.resizeObserver.disconnect()},template:"\n <div class='q-fittext' ref='container' :style=rootStyle\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\n "})}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=fittext-vue.min.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"fittext-vue.min.js","sources":["../../src/util/fittext-vue.js"],"sourcesContent":["/*eslint no-unused-vars: warn */\n/*eslint max-len: [\"error\", { \"code\": 160 }] */\n/*eslint-disable no-trailing-spaces */\n/*eslint-env es6*/\n\nimport {calc} from \"./css-calc\";\nimport fitty from \"./fitty\";\nimport {textFit} from \"./textfit\";\n\nexport default {\n install(Vue/*,options*/){\n Vue.component('fittext',{\n props: {\n maxsize: {\n type: String,\n default: \"512px\",\n },\n minsize: {\n type: String,\n default: \"10px\",\n },\n vertical: Boolean,\n singleline: Boolean,\n dynamic: Boolean,\n },\n data() {\n return {\n resizeObserver: null,\n mutationObserver: null,\n };\n },\n computed: {\n rootStyle() {\n if (this.vertical) {\n return `height: 100%;`;\n } else {\n return `width: 100%;`;\n }\n }\n },\n methods: {\n },\n mounted() {\n const self = this;\n // If the content could change after initial presentation,\n // Use the fitty method. It is slightly worse on multiline horizontal text,\n // but better supports content that can change later on.\n fitty(self.$refs.text,\n {\n minSize: calc(self.minsize),\n maxSize: calc(self.maxsize),\n vertical: self.vertical,\n multiline: !self.singleline,\n });\n /*\n } else {\n // Since the method textFit uses does not do well with\n // content that is altered after the initial change, but it does to better\n // with vertically aligned text\n textFit(self.$refs.text,{\n multiLine: !self.singleline, // if true, textFit will not set white-space: no-wrap\n detectMultiLine: false, // disable to turn off automatic multi-line sensing\n minFontSize: calc(self.minsize),\n maxFontSize: calc(self.maxsize),\n reProcess: true, \n widthOnly: !self.vertical,\n });\n } */\n },\n unmounted() {\n if(this.mutationObserver) {\n this.mutationObserver.disconnect();\n }\n if(this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n },\n template: `\n <div class='q-fittext' ref='container' :style=rootStyle\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\n `,\n });\n },\n};"],"names":["install","Vue","component","props","maxsize","type","String","default","minsize","vertical","Boolean","singleline","dynamic","data","resizeObserver","mutationObserver","computed","rootStyle","this","methods","mounted","$refs","text","minSize","maxSize","multiline","unmounted","disconnect","template"],"mappings":"qSASe,CACXA,QAAQC,KACJA,IAAIC,UAAU,UAAU,CACxBC,MAAO,CACHC,QAAS,CACLC,KAAMC,OACNC,QAAS,SAEbC,QAAS,CACLH,KAAMC,OACNC,QAAS,QAEbE,SAAUC,QACVC,WAAYD,QACZE,QAASF,SAEbG,KAAI,KACO,CACHC,eAAgB,KAChBC,iBAAkB,OAG1BC,SAAU,CACNC,mBACQC,KAAKT,SACG,gBAEA,iBAIpBU,QAAS,GAETC,6BACiBF,KAIFG,MAAMC,KACb,CACAC,SAAS,iBANAL,KAMUV,SACnBgB,SAAS,iBAPAN,KAOUd,SACnBK,SARSS,KAQMT,SACfgB,WATSP,KASQP,cAiBzBe,YACOR,KAAKH,uBACCA,iBAAiBY,aAEvBT,KAAKJ,qBACCA,eAAea,cAG5BC,SAAW"}

3
amd/build/util/fitty.min.js vendored Normal file
View file

@ -0,0 +1,3 @@
define("local_treestudyplan/util/fitty",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;var _default=(w=>{if(!w)return;const DrawState={IDLE:0,DIRTY_CONTENT:1,DIRTY_LAYOUT:2,DIRTY:3};let fitties=[],redrawFrame=null;const requestRedraw="requestAnimationFrame"in w?function(){let options=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{sync:!1};w.cancelAnimationFrame(redrawFrame);const redrawFn=()=>redraw(fitties.filter((f=>f.dirty&&f.active)));if(options.sync)return redrawFn();redrawFrame=w.requestAnimationFrame(redrawFn)}:()=>{},redrawAll=type=>options=>{fitties.forEach((f=>f.dirty=type)),requestRedraw(options)},redraw=fitties=>{fitties.filter((f=>!f.styleComputed)).forEach((f=>{f.styleComputed=computeStyle(f)})),fitties.filter(shouldPreStyle).forEach(applyStyle);const fittiesToRedraw=fitties.filter(shouldRedraw);fittiesToRedraw.forEach(calculateStyles),fittiesToRedraw.forEach((f=>{applyStyle(f),markAsClean(f)})),fittiesToRedraw.forEach(dispatchFitEvent)},markAsClean=f=>f.dirty=DrawState.IDLE,calculateStyles=f=>{f.vertical?(f.availableHeight=f.element.parentNode.clientHeight,f.currentHeight=f.element.scrollHeight,f.previousFontSize=f.currentFontSize,f.currentFontSize=Math.min(Math.max(f.minSize,f.availableHeight/f.currentHeight*f.previousFontSize),f.maxSize)):(f.availableWidth=f.element.parentNode.clientWidth,f.currentWidth=f.element.scrollWidth,f.previousFontSize=f.currentFontSize,f.currentFontSize=Math.min(Math.max(f.minSize,f.availableWidth/f.currentWidth*f.previousFontSize),f.maxSize)),f.whiteSpace=f.multiLine&&f.currentFontSize===f.minSize?"normal":"nowrap"},shouldRedraw=f=>f.vertical?f.dirty!==DrawState.DIRTY_LAYOUT||f.dirty===DrawState.DIRTY_LAYOUT&&f.element.parentNode.clientHeight!==f.availableHeight:f.dirty!==DrawState.DIRTY_LAYOUT||f.dirty===DrawState.DIRTY_LAYOUT&&f.element.parentNode.clientWidth!==f.availableWidth,computeStyle=f=>{const style=w.getComputedStyle(f.element,null);return f.currentFontSize=parseFloat(style.getPropertyValue("font-size")),f.display=style.getPropertyValue("display"),f.whiteSpace=style.getPropertyValue("white-space"),!0},shouldPreStyle=f=>{let preStyle=!1;return!f.preStyleTestCompleted&&(/inline-/.test(f.display)||(preStyle=!0,f.display="inline-block"),"nowrap"!==f.whiteSpace&&(preStyle=!0,f.whiteSpace="nowrap"),f.preStyleTestCompleted=!0,preStyle)},applyStyle=f=>{f.element.style.whiteSpace=f.whiteSpace,f.element.style.display=f.display,f.element.style.fontSize=f.currentFontSize+"px"},dispatchFitEvent=f=>{f.element.dispatchEvent(new CustomEvent("fit",{detail:{oldValue:f.previousFontSize,newValue:f.currentFontSize,scaleFactor:f.currentFontSize/f.previousFontSize}}))},fit=(f,type)=>options=>{f.dirty=type,f.active&&requestRedraw(options)},init=f=>{f.originalStyle={whiteSpace:f.element.style.whiteSpace,display:f.element.style.display,fontSize:f.element.style.fontSize},observeMutations(f),f.newbie=!0,f.dirty=!0,fitties.push(f)},destroy=f=>()=>{fitties=fitties.filter((_=>_.element!==f.element)),f.observeMutations&&f.observer.disconnect(),f.element.style.whiteSpace=f.originalStyle.whiteSpace,f.element.style.display=f.originalStyle.display,f.element.style.fontSize=f.originalStyle.fontSize},subscribe=f=>()=>{f.active||(f.active=!0,requestRedraw())},unsubscribe=f=>()=>f.active=!1,observeMutations=f=>{f.observeMutations&&(f.observer=new MutationObserver(fit(f,DrawState.DIRTY_CONTENT)),f.observer.observe(f.element,f.observeMutations))},defaultOptions={minSize:16,maxSize:512,multiLine:!0,vertical:!1,observeMutations:"MutationObserver"in w&&{subtree:!0,childList:!0,characterData:!0}};function fittyCreate(elements,options){const fittyOptions=Object.assign({},defaultOptions,options),publicFitties=elements.map((element=>{const f=Object.assign({},fittyOptions,{element:element,active:!0});return init(f),{element:element,fit:fit(f,DrawState.DIRTY),unfreeze:subscribe(f),freeze:unsubscribe(f),unsubscribe:destroy(f)}}));return requestRedraw(),publicFitties}function fitty(target){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"string"==typeof target?fittyCreate((nl=document.querySelectorAll(target),[].slice.call(nl)),options):fittyCreate([target],options)[0];var nl}let resizeDebounce=null;const onWindowResized=()=>{w.clearTimeout(resizeDebounce),resizeDebounce=w.setTimeout(redrawAll(DrawState.DIRTY_LAYOUT),fitty.observeWindowDelay)},events=["resize","orientationchange"];return Object.defineProperty(fitty,"observeWindow",{set:enabled=>{const method=(enabled?"add":"remove")+"EventListener";events.forEach((e=>{w[method](e,onWindowResized)}))}}),fitty.observeWindow=!0,fitty.observeWindowDelay=100,fitty.fitAll=redrawAll(DrawState.DIRTY),fitty})("undefined"==typeof window?null:window);return _exports.default=_default,_exports.default}));
//# sourceMappingURL=fitty.min.js.map

File diff suppressed because one or more lines are too long

3
amd/build/util/textfit.min.js vendored Normal file
View file

@ -0,0 +1,3 @@
define("local_treestudyplan/util/textfit",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.textFit=function(els,options){options||(options={});var settings={};for(var key in defaultSettings)options.hasOwnProperty(key)?settings[key]=options[key]:settings[key]=defaultSettings[key];"function"==typeof els.toArray&&(els=els.toArray());var elType=Object.prototype.toString.call(els);"[object Array]"!==elType&&"[object NodeList]"!==elType&&"[object HTMLCollection]"!==elType&&(els=[els]);for(var i=0;i<els.length;i++)processItem(els[i],settings)};var defaultSettings={alignVert:!1,alignHoriz:!1,multiLine:!1,detectMultiLine:!0,minFontSize:6,maxFontSize:80,reProcess:!0,widthOnly:!1,alignVertWithFlexbox:!1};function processItem(el,settings){if(o=el,!("object"==typeof HTMLElement?o instanceof HTMLElement:o&&"object"==typeof o&&null!==o&&1===o.nodeType&&"string"==typeof o.nodeName)||!settings.reProcess&&el.getAttribute("textFitted"))return!1;var o,innerSpan,originalHeight,originalHTML,originalWidth,low,mid,high;if(settings.reProcess||el.setAttribute("textFitted",1),originalHTML=el.innerHTML,originalWidth=function(el){var style=window.getComputedStyle(el,null);return el.getBoundingClientRect().width-parseInt(style.getPropertyValue("padding-left"),10)-parseInt(style.getPropertyValue("padding-right"),10)}(el),originalHeight=function(el){var style=window.getComputedStyle(el,null);return el.getBoundingClientRect().height-parseInt(style.getPropertyValue("padding-top"),10)-parseInt(style.getPropertyValue("padding-bottom"),10)}(el),!originalWidth||!settings.widthOnly&&!originalHeight)throw settings.widthOnly?new Error("Set a static width on the target element "+el.outerHTML+" before using textFit!"):new Error("Set a static height and width on the target element "+el.outerHTML+" before using textFit!");-1===originalHTML.indexOf("textFitted")?((innerSpan=document.createElement("span")).className="textFitted",innerSpan.style.display="inline-block",innerSpan.innerHTML=originalHTML,el.innerHTML="",el.appendChild(innerSpan)):hasClass(innerSpan=el.querySelector("span.textFitted"),"textFitAlignVert")&&(innerSpan.className=innerSpan.className.replace("textFitAlignVert",""),innerSpan.style.height="",el.className.replace("textFitAlignVertFlex","")),settings.alignHoriz&&(el.style["text-align"]="center",innerSpan.style["text-align"]="center");var multiLine=settings.multiLine;settings.detectMultiLine&&!multiLine&&innerSpan.getBoundingClientRect().height>=2*parseInt(window.getComputedStyle(innerSpan)["font-size"],10)&&(multiLine=!0),multiLine||(el.style["white-space"]="nowrap"),low=settings.minFontSize,high=settings.maxFontSize;for(var size=low;low<=high;){mid=(high+low)/2,innerSpan.style.fontSize=mid+"px";var innerSpanBoundingClientRect=innerSpan.getBoundingClientRect();innerSpanBoundingClientRect.width<=originalWidth&&(settings.widthOnly||innerSpanBoundingClientRect.height<=originalHeight)?(size=mid,low=mid+1):high=mid-1}if(innerSpan.style.fontSize!=size+"px"&&(innerSpan.style.fontSize=size+"px"),settings.alignVert){!function(){if(document.getElementById("textFitStyleSheet"))return;var style=[".textFitAlignVert{","position: absolute;","top: 0; right: 0; bottom: 0; left: 0;","margin: auto;","display: flex;","justify-content: center;","flex-direction: column;","}",".textFitAlignVertFlex{","display: flex;","}",".textFitAlignVertFlex .textFitAlignVert{","position: static;","}"].join(""),css=document.createElement("style");css.type="text/css",css.id="textFitStyleSheet",css.innerHTML=style,document.body.appendChild(css)}();var height=innerSpan.scrollHeight;"static"===window.getComputedStyle(el).position&&(el.style.position="relative"),hasClass(innerSpan,"textFitAlignVert")||(innerSpan.className=innerSpan.className+" textFitAlignVert"),innerSpan.style.height=height+"px",settings.alignVertWithFlexbox&&!hasClass(el,"textFitAlignVertFlex")&&(el.className=el.className+" textFitAlignVertFlex")}}function hasClass(element,cls){return(" "+element.className+" ").indexOf(" "+cls+" ")>-1}}));
//# sourceMappingURL=textfit.min.js.map

File diff suppressed because one or more lines are too long

View file

@ -489,7 +489,10 @@ export default {
} }
} }
return startpageindex; return startpageindex;
} },
wwwroot() {
return Config.wwwroot;
},
}, },
methods: { methods: {
pageduration(page){ pageduration(page){
@ -558,7 +561,7 @@ export default {
ItemEventBus.$emit('redrawLines', null); ItemEventBus.$emit('redrawLines', null);
scrollCurrentIntoView(this.selectedpage.id); scrollCurrentIntoView(this.selectedpage.id);
} },
}, },
mounted() { mounted() {
// scrollCurrentIntoView(this.selectedpage.id); // scrollCurrentIntoView(this.selectedpage.id);
@ -624,7 +627,12 @@ export default {
<div v-if="page.studylines.length > 0" class='r-studyplan-content'> <div v-if="page.studylines.length > 0" class='r-studyplan-content'>
<!-- First paint the headings--> <!-- First paint the headings-->
<div class='r-studyplan-headings' <div class='r-studyplan-headings'
><s-studyline-header-heading :identifier="Number(page.id)"></s-studyline-header-heading> ><s-studyline-header-heading :identifier="Number(page.id)"
><a v-if="selectedpage"
:href="wwwroot+'//local/treestudyplan/studyplan-report.php?page='+selectedpage.id"
target='_blank'><i class='fa fa-file-text'></i></a
></s-studyline-header-heading>
<r-studyline-heading v-for="(line,lineindex) in page.studylines" <r-studyline-heading v-for="(line,lineindex) in page.studylines"
:key="line.id" :key="line.id"
v-model="page.studylines[lineindex]" v-model="page.studylines[lineindex]"
@ -643,7 +651,11 @@ export default {
v-if="index > 0" v-if="index > 0"
v-model="page.perioddesc[index-1]" v-model="page.perioddesc[index-1]"
:identifier="Number(page.id)" :identifier="Number(page.id)"
></s-studyline-header-period> ><a v-if="selectedpage"
:href="wwwroot+'//local/treestudyplan/studyplan-report.php?page='+selectedpage.id
+'&firstperiod='+index+'&lastperiod='+index"
target='_blank'><i class='fa fa-file-text'></i></a
></s-studyline-header-period>
<div class="s-studyline-header-filter"></div> <div class="s-studyline-header-filter"></div>
</template> </template>

View file

@ -12,6 +12,7 @@ import notification from 'core/notification';
import Debugger from './util/debugger'; import Debugger from './util/debugger';
import Config from 'core/config'; import Config from 'core/config';
import TSComponents from './treestudyplan-components'; import TSComponents from './treestudyplan-components';
import FitTextVue from './util/fittext-vue';
const debug = new Debugger("treestudyplan-viewer"); const debug = new Debugger("treestudyplan-viewer");
@ -29,8 +30,8 @@ const LINE_GRAVITY = 1.3;
function conditionHeaders(item) { function conditionHeaders(item) {
const course = item.course; const course = item.course;
const list = []; const list = [];
if (course.completion) { if (course.competency) {
for (const cmp of course.competencies) { for (const cmp of course.competency.competencies) {
list.push({ list.push({
name: cmp.title, name: cmp.title,
}); });
@ -46,14 +47,12 @@ function conditionHeaders(item) {
} else if(course.grades) { } else if(course.grades) {
for(const g of course.grades) { for(const g of course.grades) {
if (g.selected) { if (g.selected) {
debug.info("...included");
list.push({ list.push({
name: g.name, name: g.name,
}); });
} }
} }
} }
debug.info("Returning list",list);
return list; return list;
} }
@ -64,8 +63,8 @@ function conditionHeaders(item) {
function conditions(item) { function conditions(item) {
const course = item.course; const course = item.course;
const list = []; const list = [];
if (course.completion) { if (course.competency) {
for (const cmp of course.competencies) { for (const cmp of course.competency.competencies) {
list.push(cmp); list.push(cmp);
} }
} else if(course.completion) { } else if(course.completion) {
@ -88,6 +87,7 @@ function conditions(item) {
export default { export default {
install(Vue/*,options*/){ install(Vue/*,options*/){
Vue.use(TSComponents); Vue.use(TSComponents);
Vue.use(FitTextVue);
let strings = load_strings({ let strings = load_strings({
report: { report: {
@ -153,6 +153,7 @@ export default {
immediate: true, immediate: true,
handler (structure) { handler (structure) {
// (Re)build expansion info structure // (Re)build expansion info structure
let firstperiod = true;
for (const period of structure.periods) { for (const period of structure.periods) {
const pid = period.period.id; const pid = period.period.id;
if (!this.expansioninfo.periods[pid]) { if (!this.expansioninfo.periods[pid]) {
@ -161,7 +162,7 @@ export default {
this.expansioninfo.periods, this.expansioninfo.periods,
pid, pid,
{ {
expanded: false, expanded: (firstperiod?true:false),
} }
); );
this.$set( this.$set(
@ -195,6 +196,7 @@ export default {
} }
} }
} }
firstperiod = false;
} }
} }
} }
@ -208,16 +210,16 @@ export default {
for (const period of this.structure.periods) { for (const period of this.structure.periods) {
const pid = period.period.id; const pid = period.period.id;
if (!this.expansioninfo.periods[pid].expanded) { if (!this.expansioninfo.periods[pid].expanded) {
// This period is not expanded. Just one // This period is not expanded. Make it 3 units wide
count += 1; count += 3;
} else { } else {
for (const line of period.lines) { for (const line of period.lines) {
const lid = line.line.id; const lid = line.line.id;
if (!this.expansioninfo.lines[pid][lid]) { if (!this.expansioninfo.lines[pid][lid].expanded) {
count +=1; count +=1;
} else { } else {
for (const item of line.items) { for (const item of line.items) {
if (!this.expansioninfo.items[item.id]) { if (!this.expansioninfo.items[item.id].expanded) {
count += 1; count += 1;
} else { } else {
count += 1 + conditions(item).length; count += 1 + conditions(item).length;
@ -290,7 +292,8 @@ export default {
}, },
template: ` template: `
<table class='q-studyplanreport'> <table class='q-studyplanreport'
:style="'--resultColCount: '+resultColCount+';'">
<q-header <q-header
:sorting='sorting' :sorting='sorting'
:structure='structure' :structure='structure'
@ -338,7 +341,6 @@ export default {
}, },
colspanPeriod(period) { colspanPeriod(period) {
const pid = period.period.id; const pid = period.period.id;
debug.info("Checking period expansion",period,this.expansion.periods);
if (this.expansion.periods[pid].expanded) { if (this.expansion.periods[pid].expanded) {
let sum = 0; let sum = 0;
for (const l of period.lines) { for (const l of period.lines) {
@ -370,8 +372,26 @@ export default {
} else { } else {
return 1; return 1;
} }
} },
togglePeriod(period,val) {
if ( val === undefined) {
val = !(this.expansion.periods[period.id].expanded);
}
this.$emit('expansion','periods',period.id,val);
},
toggleLine(period,line,val) {
if ( val === undefined) {
val = !(this.expansion.lines[period.id][line.id].expanded);
}
this.$emit('expansion','lines',[period.id,line.id],val);
},
toggleItem(item,val) {
if ( val === undefined) {
val = !(this.expansion.items[item.id].expanded);
}
debug.info("Toggle item",item,val);
this.$emit('expansion','items',item.id,val);
},
}, },
mounted() { mounted() {
@ -387,25 +407,29 @@ export default {
<tr> <!-- period heading --> <tr> <!-- period heading -->
<th rowspan='4' class='q-studentname'><span>{{text.students}}</span></th> <th rowspan='4' class='q-studentname'><span>{{text.students}}</span></th>
<th v-for="p in structure.periods" <th v-for="p in structure.periods"
class='q-period-heading' :class="'q-period-heading '+ ((expansion.periods[p.period.id].expanded)?'expanded':'collapsed')"
:colspan='colspanPeriod(p)' :colspan='colspanPeriod(p)'
:rowspan='(expansion.periods[p.period.id].expanded)?1:4' :rowspan='(expansion.periods[p.period.id].expanded)?1:4'
><a v-if="expansion.periods[p.period.id].expanded" ><span class="q-wrap"><a href='#' @click.prevent="togglePeriod(p.period)"
href='#' @click.prevent="$emit('expansion','periods',p.period.id,false);" ><i v-if="expansion.periods[p.period.id].expanded"
><i class='fa fa-minus-square'></i></a class='q-chevron fa fa-chevron-down'></i
><a v-else ><i v-else class='q-chevron fa fa-chevron-right'></i
href='#' @click.prevent="$emit('expansion','periods',p.period.id,true);" >&nbsp;{{ p.period.fullname}}</a></span
><i class='fa fa-plus-square'></i></a ></th>
>&nbsp;{{ p.period.fullname}}
</th>
</tr> </tr>
<tr> <!-- line heading --> <tr> <!-- line heading -->
<template v-for="p in structure.periods"> <template v-for="p in structure.periods">
<template v-if="expansion.periods[p.period.id].expanded"> <template v-if="expansion.periods[p.period.id].expanded">
<th v-for="l in p.lines" <th v-for="l in p.lines"
class='q-line-heading' :class="'q-line-heading ' + ((expansion.lines[p.period.id][l.line.id].expanded)?'expanded':'collapsed')"
:colspan="colspanLine(p,l)" :colspan="colspanLine(p,l)"
><span v-html="l.line.shortname"></span :rowspan='(expansion.lines[p.period.id][l.line.id].expanded)?1:3'
><span class="q-wrap"><fittext vertical maxsize="18pt"
><span class='q-label'
:title="l.line.shortname"
v-html="l.line.shortname"
></span
></fittext></span
></th> ></th>
</template> </template>
</template> </template>
@ -419,13 +443,22 @@ export default {
:class="'q-item-heading ' + ((expansion.items[item.id].expanded)?'expanded':'collapsed')" :class="'q-item-heading ' + ((expansion.items[item.id].expanded)?'expanded':'collapsed')"
:colspan="colspanItem(item)" :colspan="colspanItem(item)"
:rowspan='(expansion.items[item.id].expanded)?1:2' :rowspan='(expansion.items[item.id].expanded)?1:2'
><a v-if="expansion.items[item.id].expanded" ><span class="q-wrap"><a href='#'
href='#' @click.prevent="$emit('expansion','items',item.id,false);" @click.prevent="toggleItem(item)"
><i class='fa fa-minus-square'></i></a ><i v-if="expansion.items[item.id].expanded"
><a v-else class='q-chevron fa fa-chevron-down'></i
href='#' @click.prevent="$emit('expansion','items',item.id,true);" ><i v-else
><i class='fa fa-plus-square'></i></a class='q-chevron fa fa-chevron-right'></i
>&nbsp;<span v-html="item.course.displayname"></span ></a
>&nbsp;<a style="display: inline-block;" href='#'
@click.prevent="toggleItem(item)"
><fittext vertical maxsize="18pt" singleline
><span class='q-label'
:title="item.course.displayname"
v-html="item.course.displayname"
></span
></fittext
></a></span
></th> ></th>
</template> </template>
</template> </template>
@ -440,10 +473,13 @@ export default {
<template v-for="item in l.items"> <template v-for="item in l.items">
<template v-if="expansion.items[item.id].expanded"> <template v-if="expansion.items[item.id].expanded">
<th class='q-condition-heading overall' <th class='q-condition-heading overall'
><span>{{ text.overall }}</span></th> ><span class='q-wrap'>{{ text.overall }}</span></th>
<th v-for="c in conditions(item)" <th v-for="c in conditions(item)"
class='q-condition-heading' class='q-condition-heading'
><span v-html="c.name"></span ><span class="q-wrap"><fittext vertical maxsize="18pt"><span class='q-label'
:title="c.name"
v-html="c.name"></span
></span></fittext
></th> ></th>
</template> </template>
</template> </template>
@ -673,9 +709,8 @@ export default {
return (course.completion || course.competency || course.grades); return (course.completion || course.competency || course.grades);
} }
}, },
}, completion_icon() {
methods: { const completion = this.condition_completion();
completion_icon(completion) {
switch(completion){ switch(completion){
default: // case "incomplete" default: // case "incomplete"
return "circle-o"; return "circle-o";
@ -693,24 +728,70 @@ export default {
return "check-circle"; return "check-circle";
} }
}, },
condition_value() {
const course = this.item.course;
if (course.competency) {
if (this.condition.grade) {
// Return grade if possible.
return this.condition.grade;
}
} else if(course.completion) {
if (this.condition.grade) {
// Return grade if possible.
return this.condition.grade;
}
} else if(course.grades) {
return this.condition.grade;
}
// Fallback to completion icon.
const icon = this.completion_icon();
return `<i class='fa fa-${icon}'></i>`;
},
condition_completion() {
// Unify completion information
const course = this.item.course;
if (course.competency) {
const competency = this.condition;
if (competency.proficient && competency.courseproficient) {
return "completed";
} else if (competency.proficient) {
return "completed";
} else if (competency.proficient === false) {
return "failed";
} else if (competency.progress) {
return "progress";
} else {
return "incomplete";
}
} else if(course.completion) {
return this.condition.status;
} else if(course.grades) {
return this.condition.completion;
}
}
},
methods: {
}, },
// TODO: Show actual grades when relevant at all (don;t forget the grade point completion requirement) // TODO: Show actual grades when relevant at all (don;t forget the grade point completion requirement)
template: ` template: `
<span class='q-conditionresult'> <span class='q-conditionresult'>
<template v-if="loading"> <fittext maxsize="10pt" singleline dynamic>
<div class="spinner-border spinner-border-sm text-info" role="status"></div> <template v-if="loading">
</template> <div class="spinner-border spinner-border-sm text-info" role="status"></div>
<template v-else-if='!item.course.enrolled'> </template>
<i v-b-popover.top <template v-else-if='!item.course.enrolled'>
class="fa fa-exclamation-triangle t-not-enrolled-alert" <i class="fa fa-ellipsis-h"
:title="text.student_not_tracked"></i> :title="text.student_not_tracked"></i>
</template> </template>
<template v-else> <template v-else>
<i v-b-popover.top <span
:class="'fa fa-'+completion_icon(item.completion)+ :class="'r-completion-'+condition_completion"
' r-completion-'+item.completion" :title="text['completion_'+condition_completion]"
:title="text['completion_'+item.completion]"></i> >{{condition_value}}</span
</template> >
</template>
</fittext>
</span> </span>
`, `,
}); });

View file

@ -210,7 +210,7 @@ export default {
} }
}, },
template: ` template: `
<div class="s-studyline-header-heading" ref="main" :data-id="identifier"></div> <div class="s-studyline-header-heading" ref="main" :data-id="identifier"><slot></slot></div>
`, `,
}); });

198
amd/src/util/css-calc.js Normal file
View file

@ -0,0 +1,198 @@
/*eslint no-trailing-spaces: "off"*/
/*eslint no-eval: "off"*/
/***********************************
* Licence: MIT
* (c) 2023 Morglod/jchnkl
* converted from the typescript @ https://github.com/Morglod/csscalc/
*/
// units -> pixels
export const Absolute = {
/** browser version of pixel */
px: 1,
/** One centimeter. 1cm = 96px/2.54 */
cm: 96 / 2.54,
/** One millimeter. 1mm = 1/10th of 1cm */
mm: 96 / 25.4,
/** One quarter of a millimeter. 1Q = 1/40th of 1cm */
Q: 96 / 101.6,
/** One inch. 1in = 2.54cm = 96px */
in: 96,
/** One pica. 1pc = 12pt = 1/6th of 1in */
pc: 96 / 6,
/** One point. 1pt = 1/72nd of 1in */
pt: 96 / 72
};
// units ->(calc context)-> pixels
export const Relative = {
/**
* Equal to 1% of the height of the viewport
* @param {number} count
* @param {object} ctx
*/
vh: (count = 1, ctx) => {
return ((ctx ? ctx.viewportHeight : window.innerHeight) / 100) * count;
},
/**
* Equal to 1% of the width of the viewport
* @param {number} count
* @param {object} ctx
*/
vw: (count = 1, ctx) => {
return ((ctx ? ctx.viewportWidth : window.innerWidth) / 100) * count;
},
/**
* 1/100th of the smallest viewport side
* @param {number} count
* @param {object} ctx
*/
vmin: (count = 1, ctx) => {
return (
((ctx
? Math.min(ctx.viewportWidth, ctx.viewportHeight)
: Math.min(window.innerWidth, window.innerHeight)) /
100) *
count
);
},
/**
* 1/100th of the largest viewport side
* @param {number} count
* @param {object} ctx
*/
vmax: (count = 1, ctx) => {
return (
((ctx
? Math.max(ctx.viewportWidth, ctx.viewportHeight)
: Math.max(window.innerWidth, window.innerHeight)) /
100) *
count
);
},
/**
* Represents the font-size of <html> element
* @param {number} count
* @param {object} ctx
*/
rem: (count = 1, ctx) => {
return (
(ctx
? ctx.htmlFontSize
: parseFloat(
window.getComputedStyle(document.querySelector("html")).fontSize
)) * count
);
},
/**
* percent of width
* @param {number} count
* @param {object} ctx
*/
"%w": (count = 1, ctx) => {
return ((ctx ? ctx.width : document.body.clientWidth) / 100) * count;
},
/**
* percent of height
* @param {number} count
* @param {object} ctx
*/
"%h": (count = 1, ctx) => {
return ((ctx ? ctx.height : document.body.clientHeight) / 100) * count;
}
};
export const Units = {
...Relative,
...Absolute
};
export const UnitRegexpStr = `(?:\\s|^)(\\d*(?:\\.\\d+)?)(${Object.keys(
Units
).join("|")})(?:\\s|$|\\n)`;
export const UnitRegexp = new RegExp(UnitRegexpStr);
export const UnitRegexpGM = new RegExp(UnitRegexpStr, "gm");
/**
*
* @param {*} count
* @param {*} fromUnits
* @param {*} toUnits
* @param {*} ctx
* @returns
*/
export function convert(count, fromUnits, toUnits, ctx = calcCtx()) {
const baseUnit = Units[fromUnits];
const basePx =
typeof baseUnit === "function" ? baseUnit(count, ctx) : baseUnit * count;
const dstUnit = Units[toUnits];
const dstBasePx = typeof dstUnit === "function" ? dstUnit(1, ctx) : dstUnit;
return basePx / dstBasePx;
}
/**
*
* @param {*} expr
* @param {*} toUnits
* @param {*} ctx
* @returns
*/
export function convertAllInStr(expr, toUnits, ctx = calcCtx()) {
return expr.replace(UnitRegexpGM, (substr, count, unit) => {
return convert(parseFloat(count), unit, toUnits, ctx).toString();
});
}
/**
*
* @param {*} el
* @returns
*/
export function calcCtx(el) {
if (el) {
const rect = el.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight,
htmlFontSize: parseFloat(
window.getComputedStyle(document.querySelector("html")).fontSize
),
};
} else {
return {
width: document.body.clientWidth,
height: document.body.clientHeight,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight,
htmlFontSize: parseFloat(
window.getComputedStyle(document.querySelector("html")).fontSize
)
};
}
}
/**
*
* @param {*} expression
* @param {*} el_ctx
* @param {*} ctx
* @returns
*/
export function calc(expression, el_ctx, ctx) {
if (el_ctx === undefined) {ctx = calcCtx(); }
else {
if (el_ctx instanceof HTMLElement) {
if (!ctx) {ctx = calcCtx(el_ctx); }
} else {
ctx = el_ctx;
}
}
return eval(convertAllInStr(expression, "px", ctx));
}

View file

@ -0,0 +1,86 @@
/*eslint no-unused-vars: warn */
/*eslint max-len: ["error", { "code": 160 }] */
/*eslint-disable no-trailing-spaces */
/*eslint-env es6*/
import {calc} from "./css-calc";
import fitty from "./fitty";
import {textFit} from "./textfit";
export default {
install(Vue/*,options*/){
Vue.component('fittext',{
props: {
maxsize: {
type: String,
default: "512px",
},
minsize: {
type: String,
default: "10px",
},
vertical: Boolean,
singleline: Boolean,
dynamic: Boolean,
},
data() {
return {
resizeObserver: null,
mutationObserver: null,
};
},
computed: {
rootStyle() {
if (this.vertical) {
return `height: 100%;`;
} else {
return `width: 100%;`;
}
}
},
methods: {
},
mounted() {
const self = this;
// If the content could change after initial presentation,
// Use the fitty method. It is slightly worse on multiline horizontal text,
// but better supports content that can change later on.
fitty(self.$refs.text,
{
minSize: calc(self.minsize),
maxSize: calc(self.maxsize),
vertical: self.vertical,
multiline: !self.singleline,
});
/*
} else {
// Since the method textFit uses does not do well with
// content that is altered after the initial change, but it does to better
// with vertically aligned text
textFit(self.$refs.text,{
multiLine: !self.singleline, // if true, textFit will not set white-space: no-wrap
detectMultiLine: false, // disable to turn off automatic multi-line sensing
minFontSize: calc(self.minsize),
maxFontSize: calc(self.maxsize),
reProcess: true,
widthOnly: !self.vertical,
});
} */
},
unmounted() {
if(this.mutationObserver) {
this.mutationObserver.disconnect();
}
if(this.resizeObserver) {
this.resizeObserver.disconnect();
}
},
template: `
<div class='q-fittext' ref='container' :style=rootStyle">
<span :style="'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'" class='q-fittext-text' ref='text'><slot></slot>
</span
></div>
`,
});
},
};

369
amd/src/util/fitty.js Normal file
View file

@ -0,0 +1,369 @@
/*eslint no-console: "off"*/
/*
Copyright (c) 2017-2021 Rik Schennink - All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export default ((w) => {
// no window, early exit
if (!w) { return; }
// node list to array helper method
const toArray = (nl) => [].slice.call(nl);
// states
const DrawState = {
IDLE: 0,
DIRTY_CONTENT: 1,
DIRTY_LAYOUT: 2,
DIRTY: 3,
};
// all active fitty elements
let fitties = [];
// group all redraw calls till next frame, we cancel each frame request when a new one comes in.
// If no support for request animation frame, this is an empty function and supports for fitty stops.
let redrawFrame = null;
const requestRedraw =
'requestAnimationFrame' in w
? (options = { sync: false }) => {
w.cancelAnimationFrame(redrawFrame);
const redrawFn = () => redraw(fitties.filter((f) => f.dirty && f.active));
if (options.sync) { return redrawFn(); }
redrawFrame = w.requestAnimationFrame(redrawFn);
}
: () => {};
// sets all fitties to dirty so they are redrawn on the next redraw loop, then calls redraw
const redrawAll = (type) => (options) => {
fitties.forEach((f) => (f.dirty = type));
requestRedraw(options);
};
// redraws fitties so they nicely fit their parent container
const redraw = (fitties) => {
// getting info from the DOM at this point should not trigger a reflow,
// let's gather as much intel as possible before triggering a reflow
// check if styles of all fitties have been computed
fitties
.filter((f) => !f.styleComputed)
.forEach((f) => {
f.styleComputed = computeStyle(f);
});
// restyle elements that require pre-styling, this triggers a reflow, please try to prevent by adding CSS rules (see docs)
fitties.filter(shouldPreStyle).forEach(applyStyle);
// we now determine which fitties should be redrawn
const fittiesToRedraw = fitties.filter(shouldRedraw);
// we calculate final styles for these fitties
fittiesToRedraw.forEach(calculateStyles);
// now we apply the calculated styles from our previous loop
fittiesToRedraw.forEach((f) => {
applyStyle(f);
markAsClean(f);
});
// now we dispatch events for all restyled fitties
fittiesToRedraw.forEach(dispatchFitEvent);
};
const markAsClean = (f) => (f.dirty = DrawState.IDLE);
const calculateStyles = (f) => {
if (f.vertical) {
// get available width from parent node
f.availableHeight = f.element.parentNode.clientHeight;
// the space our target element uses
f.currentHeight = f.element.scrollHeight;
// remember current font size
f.previousFontSize = f.currentFontSize;
// let's calculate the new font size
f.currentFontSize = Math.min(
Math.max(f.minSize, (f.availableHeight / f.currentHeight) * f.previousFontSize),
f.maxSize
);
} else {
// get available width from parent node
f.availableWidth = f.element.parentNode.clientWidth;
// the space our target element uses
f.currentWidth = f.element.scrollWidth;
// remember current font size
f.previousFontSize = f.currentFontSize;
// let's calculate the new font size
f.currentFontSize = Math.min(
Math.max(f.minSize, (f.availableWidth / f.currentWidth) * f.previousFontSize),
f.maxSize
);
}
// if allows wrapping, only wrap when at minimum font size (otherwise would break container)
f.whiteSpace = f.multiLine && f.currentFontSize === f.minSize ? 'normal' : 'nowrap';
};
// should always redraw if is not dirty layout, if is dirty layout, only redraw if size has changed
const shouldRedraw = (f) => {
if (f.vertical) {
return f.dirty !== DrawState.DIRTY_LAYOUT ||
(f.dirty === DrawState.DIRTY_LAYOUT &&
f.element.parentNode.clientHeight !== f.availableHeight);
} else {
return f.dirty !== DrawState.DIRTY_LAYOUT ||
(f.dirty === DrawState.DIRTY_LAYOUT &&
f.element.parentNode.clientWidth !== f.availableWidth);
}
};
// every fitty element is tested for invalid styles
const computeStyle = (f) => {
// get style properties
const style = w.getComputedStyle(f.element, null);
// get current font size in pixels (if we already calculated it, use the calculated version)
f.currentFontSize = parseFloat(style.getPropertyValue('font-size'));
// get display type and wrap mode
f.display = style.getPropertyValue('display');
f.whiteSpace = style.getPropertyValue('white-space');
// styles computed
return true;
};
// determines if this fitty requires initial styling, can be prevented by applying correct styles through CSS
const shouldPreStyle = (f) => {
let preStyle = false;
// if we already tested for prestyling we don't have to do it again
if (f.preStyleTestCompleted) { return false; }
// should have an inline style, if not, apply
if (!/inline-/.test(f.display)) {
preStyle = true;
f.display = 'inline-block';
}
// to correctly calculate dimensions the element should have whiteSpace set to nowrap
if (f.whiteSpace !== 'nowrap') {
preStyle = true;
f.whiteSpace = 'nowrap';
}
// we don't have to do this twice
f.preStyleTestCompleted = true;
return preStyle;
};
// apply styles to single fitty
const applyStyle = (f) => {
f.element.style.whiteSpace = f.whiteSpace;
f.element.style.display = f.display;
f.element.style.fontSize = f.currentFontSize + 'px';
};
// dispatch a fit event on a fitty
const dispatchFitEvent = (f) => {
f.element.dispatchEvent(
new CustomEvent('fit', {
detail: {
oldValue: f.previousFontSize,
newValue: f.currentFontSize,
scaleFactor: f.currentFontSize / f.previousFontSize,
},
})
);
};
// fit method, marks the fitty as dirty and requests a redraw (this will also redraw any other fitty marked as dirty)
const fit = (f, type) => (options) => {
f.dirty = type;
if (!f.active) { return; }
requestRedraw(options);
};
const init = (f) => {
// save some of the original CSS properties before we change them
f.originalStyle = {
whiteSpace: f.element.style.whiteSpace,
display: f.element.style.display,
fontSize: f.element.style.fontSize,
};
// should we observe DOM mutations
observeMutations(f);
// this is a new fitty so we need to validate if it's styles are in order
f.newbie = true;
// because it's a new fitty it should also be dirty, we want it to redraw on the first loop
f.dirty = true;
// we want to be able to update this fitty
fitties.push(f);
};
const destroy = (f) => () => {
// remove from fitties array
fitties = fitties.filter((_) => _.element !== f.element);
// stop observing DOM
if (f.observeMutations) { f.observer.disconnect(); }
// reset the CSS properties we changes
f.element.style.whiteSpace = f.originalStyle.whiteSpace;
f.element.style.display = f.originalStyle.display;
f.element.style.fontSize = f.originalStyle.fontSize;
};
// add a new fitty, does not redraw said fitty
const subscribe = (f) => () => {
if (f.active) { return; }
f.active = true;
requestRedraw();
};
// remove an existing fitty
const unsubscribe = (f) => () => (f.active = false);
const observeMutations = (f) => {
// no observing?
if (!f.observeMutations) { return; }
// start observing mutations
f.observer = new MutationObserver(fit(f, DrawState.DIRTY_CONTENT));
// start observing
f.observer.observe(f.element, f.observeMutations);
};
// default mutation observer settings
const mutationObserverDefaultSetting = {
subtree: true,
childList: true,
characterData: true,
};
// default fitty options
const defaultOptions = {
minSize: 16,
maxSize: 512,
multiLine: true,
vertical: false,
observeMutations: 'MutationObserver' in w ? mutationObserverDefaultSetting : false,
};
/**
* array of elements in, fitty instances out
* @param {Array} elements
* @param {object} options
*/
function fittyCreate(elements, options) {
// set options object
const fittyOptions = Object.assign(
{},
// expand default options
defaultOptions,
// override with custom options
options
);
// create fitties
const publicFitties = elements.map((element) => {
// create fitty instance
const f = Object.assign({}, fittyOptions, {
// internal options for this fitty
element,
active: true,
});
// initialise this fitty
init(f);
// expose API
return {
element,
fit: fit(f, DrawState.DIRTY),
unfreeze: subscribe(f),
freeze: unsubscribe(f),
unsubscribe: destroy(f),
};
});
// call redraw on newly initiated fitties
requestRedraw();
// expose fitties
return publicFitties;
}
/**
* fitty creation function
* @param {*} target
* @param {*} options
* @returns
*/
function fitty(target, options = {}) {
// if target is a string
return typeof target === 'string'
? // treat it as a querySelector
fittyCreate(toArray(document.querySelectorAll(target)), options)
: // create single fitty
fittyCreate([target], options)[0];
}
// handles viewport changes, redraws all fitties, but only does so after a timeout
let resizeDebounce = null;
const onWindowResized = () => {
w.clearTimeout(resizeDebounce);
resizeDebounce = w.setTimeout(redrawAll(DrawState.DIRTY_LAYOUT), fitty.observeWindowDelay);
};
// define observe window property, so when we set it to true or false events are automatically added and removed
const events = ['resize', 'orientationchange'];
Object.defineProperty(fitty, 'observeWindow', {
set: (enabled) => {
const method = `${enabled ? 'add' : 'remove'}EventListener`;
events.forEach((e) => {
w[method](e, onWindowResized);
});
},
});
// fitty global properties (by setting observeWindow to true the events above get added)
fitty.observeWindow = true;
fitty.observeWindowDelay = 100;
// public fit all method, will force redraw no matter what
fitty.fitAll = redrawAll(DrawState.DIRTY);
// export our fitty function, we don't want to keep it to our selves
return fitty;
})(typeof window === 'undefined' ? null : window);

251
amd/src/util/textfit.js Normal file
View file

@ -0,0 +1,251 @@
/**
* textFit v2.3.1
* Previously known as jQuery.textFit
* 11/2014 by STRML (strml.github.com)
* MIT License
*
* To use: textFit(document.getElementById('target-div'), options);
*
* Will make the *text* content inside a container scale to fit the container
* The container is required to have a set width and height
* Uses binary search to fit text with minimal layout calls.
* Version 2.0 does not use jQuery.
*/
var defaultSettings = {
alignVert: false, // if true, textFit will align vertically using css tables
alignHoriz: false, // if true, textFit will set text-align: center
multiLine: false, // if true, textFit will not set white-space: no-wrap
detectMultiLine: true, // disable to turn off automatic multi-line sensing
minFontSize: 6,
maxFontSize: 80,
reProcess: true, // if true, textFit will re-process already-fit nodes. Set to 'false' for better performance
widthOnly: false, // if true, textFit will fit text to element width, regardless of text height
alignVertWithFlexbox: false, // if true, textFit will use flexbox for vertical alignment
};
/**
* Create new textFit element(S)
* @param {*} els element or element list
* @param {*} options Options (See default settings in textfit.js)
*/
export function textFit(els, options) {
if (!options) { options = {}; }
// Extend options.
var settings = {};
for(var key in defaultSettings){
if(options.hasOwnProperty(key)){
settings[key] = options[key];
} else {
settings[key] = defaultSettings[key];
}
}
// Convert jQuery objects into arrays
if (typeof els.toArray === "function") {
els = els.toArray();
}
// Support passing a single el
var elType = Object.prototype.toString.call(els);
if (elType !== '[object Array]' && elType !== '[object NodeList]' &&
elType !== '[object HTMLCollection]'){
els = [els];
}
// Process each el we've passed.
for(var i = 0; i < els.length; i++){
processItem(els[i], settings);
}
}
/**
* The meat. Given an el, make the text inside it fit its parent.
* @param {DOMElement} el Child el.
* @param {Object} settings Options for fit.
*/
function processItem(el, settings){
if (!isElement(el) || (!settings.reProcess && el.getAttribute('textFitted'))) {
return false;
}
// Set textFitted attribute so we know this was processed.
if(!settings.reProcess){
el.setAttribute('textFitted', 1);
}
var innerSpan, originalHeight, originalHTML, originalWidth;
var low, mid, high;
// Get element data.
originalHTML = el.innerHTML;
originalWidth = innerWidth(el);
originalHeight = innerHeight(el);
// Don't process if we can't find box dimensions
if (!originalWidth || (!settings.widthOnly && !originalHeight)) {
if(!settings.widthOnly) {
throw new Error('Set a static height and width on the target element ' + el.outerHTML +
' before using textFit!');
} else {
throw new Error('Set a static width on the target element ' + el.outerHTML +
' before using textFit!');
}
}
// Add textFitted span inside this container.
if (originalHTML.indexOf('textFitted') === -1) {
innerSpan = document.createElement('span');
innerSpan.className = 'textFitted';
// Inline block ensure it takes on the size of its contents, even if they are enclosed
// in other tags like <p>
innerSpan.style['display'] = 'inline-block';
innerSpan.innerHTML = originalHTML;
el.innerHTML = '';
el.appendChild(innerSpan);
} else {
// Reprocessing.
innerSpan = el.querySelector('span.textFitted');
// Remove vertical align if we're reprocessing.
if (hasClass(innerSpan, 'textFitAlignVert')){
innerSpan.className = innerSpan.className.replace('textFitAlignVert', '');
innerSpan.style['height'] = '';
el.className.replace('textFitAlignVertFlex', '');
}
}
// Prepare & set alignment
if (settings.alignHoriz) {
el.style['text-align'] = 'center';
innerSpan.style['text-align'] = 'center';
}
// Check if this string is multiple lines
// Not guaranteed to always work if you use wonky line-heights
var multiLine = settings.multiLine;
if (settings.detectMultiLine && !multiLine &&
innerSpan.getBoundingClientRect().height >= parseInt(window.getComputedStyle(innerSpan)['font-size'], 10) * 2){
multiLine = true;
}
// If we're not treating this as a multiline string, don't let it wrap.
if (!multiLine) {
el.style['white-space'] = 'nowrap';
}
low = settings.minFontSize;
high = settings.maxFontSize;
// Binary search for highest best fit
var size = low;
while (low <= high) {
mid = (high + low) / 2;
innerSpan.style.fontSize = mid + 'px';
var innerSpanBoundingClientRect = innerSpan.getBoundingClientRect();
if (
innerSpanBoundingClientRect.width <= originalWidth
&& (settings.widthOnly || innerSpanBoundingClientRect.height <= originalHeight)
) {
size = mid;
low = mid + 1;
} else {
high = mid - 1;
}
// await injection point
}
// found, updating font if differs:
if( innerSpan.style.fontSize != size + 'px' ) { innerSpan.style.fontSize = size + 'px'; }
// Our height is finalized. If we are aligning vertically, set that up.
if (settings.alignVert) {
addStyleSheet();
var height = innerSpan.scrollHeight;
if (window.getComputedStyle(el)['position'] === "static"){
el.style['position'] = 'relative';
}
if (!hasClass(innerSpan, "textFitAlignVert")){
innerSpan.className = innerSpan.className + " textFitAlignVert";
}
innerSpan.style['height'] = height + "px";
if (settings.alignVertWithFlexbox && !hasClass(el, "textFitAlignVertFlex")) {
el.className = el.className + " textFitAlignVertFlex";
}
}
}
/**
* Calculate height without padding.
* @param {*} el
* @returns
*/
function innerHeight(el){
var style = window.getComputedStyle(el, null);
return el.getBoundingClientRect().height -
parseInt(style.getPropertyValue('padding-top'), 10) -
parseInt(style.getPropertyValue('padding-bottom'), 10);
}
/**
* Calculate width without padding.
* @param {*} el
* @returns
*/
function innerWidth(el){
var style = window.getComputedStyle(el, null);
return el.getBoundingClientRect().width -
parseInt(style.getPropertyValue('padding-left'), 10) -
parseInt(style.getPropertyValue('padding-right'), 10);
}
/**
* Returns true if it is a DOM element
* @param {*} o
* @returns
*/
function isElement(o){
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
);
}
/**
* Check if element has a specific class
* @param {*} element
* @param {*} cls
* @returns
*/
function hasClass(element, cls) {
return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
// Better than a stylesheet dependency
/**
* Add stylesheet to the page for this
* @returns
*/
function addStyleSheet() {
if (document.getElementById("textFitStyleSheet")) { return; }
var style = [
".textFitAlignVert{",
"position: absolute;",
"top: 0; right: 0; bottom: 0; left: 0;",
"margin: auto;",
"display: flex;",
"justify-content: center;",
"flex-direction: column;",
"}",
".textFitAlignVertFlex{",
"display: flex;",
"}",
".textFitAlignVertFlex .textFitAlignVert{",
"position: static;",
"}",].join("");
var css = document.createElement("style");
css.type = "text/css";
css.id = "textFitStyleSheet";
css.innerHTML = style;
document.body.appendChild(css);
}

View file

@ -276,7 +276,7 @@ class corecompletioninfo {
See moodle/completion/criteria/completion_criteria_*.php::get_details() for the code that See moodle/completion/criteria/completion_criteria_*.php::get_details() for the code that
the code below is based on. the code below is based on.
*/ */
unset($title); // Clear title from previous iteration if it was set.
if ($type == COMPLETION_CRITERIA_TYPE_SELF) { if ($type == COMPLETION_CRITERIA_TYPE_SELF) {
$details = [ $details = [
"type" => $criteria->get_title(), "type" => $criteria->get_title(),
@ -314,6 +314,8 @@ class corecompletioninfo {
} else { } else {
$details['criteria'] = $cm->get_formatted_name(); $details['criteria'] = $cm->get_formatted_name();
} }
// Set title based on cm formatted name
$title = $cm->get_formatted_name();
// Build requirements. // Build requirements.
$details['requirement'] = array(); $details['requirement'] = array();
@ -344,14 +346,21 @@ class corecompletioninfo {
"status" => "", "status" => "",
]; ];
} else if ($type == COMPLETION_CRITERIA_TYPE_GRADE) { } else if ($type == COMPLETION_CRITERIA_TYPE_GRADE) {
$displaytype = \grade_get_setting($this->course->id, 'displaytype', $CFG->grade_displaytype);
$gradepass = $criteria->gradepass;
// Find grade item for course result.
$gi = new \grade_item(['courseid' => $this->course->id,'itemtype' => 'course']);
$displaygrade = \grade_format_gradevalue($gradepass,$gi,true,$displaytype,1);
$details = [ $details = [
"type" => get_string('coursegrade', 'completion'), "type" => get_string('coursegrade', 'completion'),
"criteria" => get_string('graderequired', 'completion'), "criteria" => get_string('graderequired', 'completion'),
// TODO: convert to selected representation (letter, percentage, etc). // TODO: convert to selected representation (letter, percentage, etc).
"requirement" => get_string('graderequired', 'completion') "requirement" => get_string('graderequired', 'completion')
.": ".format_float($criteria->gradepass, 1), .": ".$displaygrade,
"status" => "", "status" => "",
]; ];
$title = get_string('graderequired','completion').': '.$displaygrade;
} else if ($type == COMPLETION_CRITERIA_TYPE_ROLE) { } else if ($type == COMPLETION_CRITERIA_TYPE_ROLE) {
$details = [ $details = [
"type" => get_string('manualcompletionby', 'completion'), "type" => get_string('manualcompletionby', 'completion'),
@ -385,7 +394,7 @@ class corecompletioninfo {
// Only add the items list if we actually have items... // Only add the items list if we actually have items...
$cinfo["items"][] = [ $cinfo["items"][] = [
"id" => $criteria->id, "id" => $criteria->id,
"title" => $criteria->get_title_detailed(), "title" => isset($title)?$title:$criteria->get_title_detailed(),
"details" => $details, "details" => $details,
"progress" => $scanner->model($studentlist), "progress" => $scanner->model($studentlist),
]; ];
@ -530,14 +539,14 @@ class corecompletioninfo {
} else if ($type == COMPLETION_CRITERIA_TYPE_GRADE) { } else if ($type == COMPLETION_CRITERIA_TYPE_GRADE) {
// Make sure we provide the current course grade. // Make sure we provide the current course grade.
$iinfo['grade'] = floatval($iinfo['details']['status']); $rawgrade = floatval($iinfo['details']['status']);
if ($iinfo["grade"] > 0) { $iinfo['grade'] = $this->format_course_grade($rawgrade);
$iinfo["grade"] = format_float($iinfo["grade"], 1). "/". $rq = floatval($iinfo['details']['requirement']);
format_float(floatval($iinfo['details']['requirement'])); $iinfo['details']['requirement'] = $this->format_course_grade($rq);
$iinfo["status"] = $completion->is_complete() ? "complete-pass" : "complete-fail"; ;
if ($cinfo["status"] == "incomplete") { $iinfo["status"] = $completion->is_complete() ? "complete-pass" : "complete-fail";
$cinfo["status"] = "progress"; if ($cinfo["status"] == "incomplete") {
} $cinfo["status"] = "progress";
} }
if ($completion->is_complete()) { if ($completion->is_complete()) {
@ -590,22 +599,12 @@ class corecompletioninfo {
if ($gi) { if ($gi) {
// Only the following types of grade yield a result. // Only the following types of grade yield a result.
if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) { if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
$scale = $gi->load_scale();
$grade = (object)$gi->get_final($userid); // Get the grade for the specified user. $grade = (object)$gi->get_final($userid); // Get the grade for the specified user.
$result = new \stdClass; $result = new \stdClass;
// Check if the final grade is available and numeric (safety check). // Check if the final grade is available and numeric (safety check).
if (!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)) { if (!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)) {
// Convert scale grades to corresponding scale name. $result->grade = \grade_format_gradevalue($grade->finalgrade,$gi,true,null,1);
if (isset($scale)) { $result->feedback = \trim($grade->feedback);
// Get scale value.
$result->grade = $scale->get_nearest_item($grade->finalgrade);
} else {
// Round final grade to 1 decimal point.
$result->grade = round($grade->finalgrade, 1);
}
$result->feedback = trim($grade->feedback);
$result->pending = (new gradingscanner($gi))->pending($userid); $result->pending = (new gradingscanner($gi))->pending($userid);
} else { } else {
$result->grade = "-"; // Activity is gradable, but user did not receive a grade yet. $result->grade = "-"; // Activity is gradable, but user did not receive a grade yet.
@ -624,34 +623,16 @@ class corecompletioninfo {
* @param int $userid ID of user to retrieve grade for * @param int $userid ID of user to retrieve grade for
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute * @return stdClass|null object containing 'grade' and optional 'feedback' attribute
*/ */
private function get_course_grade($userid) { private function format_course_grade($grade) {
// TODO: Display grade in the way described in the course setup (with letters if needed). // TODO: Display grade in the way described in the course setup (with letters if needed).
$gi = grade_item::fetch(['itemtype' => 'course', $gi = new \grade_item(['itemtype' => 'course',
'iteminstance' => $this->course->id, 'courseid' => $this->course->id]);
'courseid' => $this->course->id]);
if ($gi) { if ($gi) {
// Only the following types of grade yield a result. return \grade_format_gradevalue($grade,$gi,true,null,1);
if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
$scale = $gi->load_scale();
$grade = $gi->get_final($userid); // Get the grade for the specified user.
// Check if the final grade is available and numeric (safety check).
if (!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)) {
// Convert scale grades to corresponding scale name.
if (isset($scale)) {
// Get scale value.
return $scale->get_nearest_item($grade->finalgrade);
} else {
// Round final grade to 1 decimal point.
return round($grade->finalgrade, 1);
}
} else {
return "-"; // User did not receive a grade yet for this course.
}
}
} }
return null; // Course cannot be graded (Shouldn't be happening, but still....). return "x"; // Course cannot be graded (Shouldn't be happening, but still....).
} }
/** /**

View file

@ -338,18 +338,8 @@ class gradeinfo {
public function user_model($userid) { public function user_model($userid) {
global $DB; global $DB;
$grade = $this->gradeitem->get_final($userid); $grade = $this->gradeitem->get_final($userid);
// Convert scale grades to corresponding scale name. // Format grade for proper display
if (!empty($grade)) { $finalgrade = \grade_format_gradevalue($grade->finalgrade,$this->gradeitem,true,null,1);
if (!is_numeric($grade->finalgrade) && empty($grade->finalgrade)) {
$finalgrade = "-";
} else if (isset($this->scale)) {
$finalgrade = $this->scale->get_nearest_item($grade->finalgrade);
} else {
$finalgrade = round($grade->finalgrade, 1);
}
} else {
$finalgrade = "-";
}
// Retrieve the aggregator and determine completion. // Retrieve the aggregator and determine completion.
if (!isset($this->studyitem)) { if (!isset($this->studyitem)) {

View file

@ -121,6 +121,7 @@ class studyplanservice extends \external_api {
$studyplan = studyplan::find_by_id($id); $studyplan = studyplan::find_by_id($id);
webservicehelper::require_capabilities([self::CAP_EDIT, self::CAP_VIEW], $studyplan->context()); webservicehelper::require_capabilities([self::CAP_EDIT, self::CAP_VIEW], $studyplan->context());
$model = $studyplan->editor_model(); $model = $studyplan->editor_model();
debug::dump($model);
return $model; return $model;
} else { } else {
return null; return null;

View file

@ -1577,68 +1577,70 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.path-local-treestudyplan { .path-local-treestudyplan {
font: inherit; font: inherit;
--border-color: var(--primary);
--conditions-bgcolor: #e7e7e7;
--courseresult-bgcolor: white;
--studentinfo-bgcolor: white;
} }
.path-local-treestudyplan table.studyplanreport { .path-local-treestudyplan table.q-studyplanreport {
table-layout: fixed; table-layout: fixed;
width: calc(12rem + var(--resultColCount) * 4rem);
} }
.path-local-treestudyplan .q-header, .path-local-treestudyplan .q-header,
.path-local-treestudyplan .q-student-results { .path-local-treestudyplan .q-student-results {
background-color: white; background-color: var(--courseresult-bgcolor);
} }
.path-local-treestudyplan .q-header .q-period-heading, .path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading, .path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading, .path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading, .path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-header .q-period-results,
.path-local-treestudyplan .q-header .q-line-results,
.path-local-treestudyplan .q-header .q-item-results,
.path-local-treestudyplan .q-header .q-condition-results,
.path-local-treestudyplan .q-student-results .q-period-heading, .path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading, .path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading, .path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading, .path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-student-results .q-period-results, border-right: 1px solid var(--border-color);
.path-local-treestudyplan .q-student-results .q-line-results, border-top: 1px solid var(--border-color);
.path-local-treestudyplan .q-student-results .q-item-results,
.path-local-treestudyplan .q-student-results .q-condition-results {
border-right: 1px solid gray;
}
.path-local-treestudyplan .q-header .q-period-heading .q-header-title,
.path-local-treestudyplan .q-header .q-line-heading .q-header-title,
.path-local-treestudyplan .q-header .q-item-heading .q-header-title,
.path-local-treestudyplan .q-header .q-condition-heading .q-header-title,
.path-local-treestudyplan .q-header .q-period-results .q-header-title,
.path-local-treestudyplan .q-header .q-line-results .q-header-title,
.path-local-treestudyplan .q-header .q-item-results .q-header-title,
.path-local-treestudyplan .q-header .q-condition-results .q-header-title,
.path-local-treestudyplan .q-student-results .q-period-heading .q-header-title,
.path-local-treestudyplan .q-student-results .q-line-heading .q-header-title,
.path-local-treestudyplan .q-student-results .q-item-heading .q-header-title,
.path-local-treestudyplan .q-student-results .q-condition-heading .q-header-title,
.path-local-treestudyplan .q-student-results .q-period-results .q-header-title,
.path-local-treestudyplan .q-student-results .q-line-results .q-header-title,
.path-local-treestudyplan .q-student-results .q-item-results .q-header-title,
.path-local-treestudyplan .q-student-results .q-condition-results .q-header-title {
padding: 0.5rem; padding: 0.5rem;
} }
.path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading.overall,
.path-local-treestudyplan .q-header .q-result.overall,
.path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading.overall,
.path-local-treestudyplan .q-student-results .q-result.overall {
background-color: var(--courseresult-bgcolor);
}
.path-local-treestudyplan .q-header .q-period-heading.collapsed,
.path-local-treestudyplan .q-student-results .q-period-heading.collapsed {
width: 12rem;
}
.path-local-treestudyplan .q-header .q-result, .path-local-treestudyplan .q-header .q-result,
.path-local-treestudyplan .q-student-results .q-result { .path-local-treestudyplan .q-student-results .q-result {
height: 2rem; height: 2rem;
padding-top: 0.25rem; padding-top: 0.25rem;
} }
.path-local-treestudyplan .q-header, .path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-student-results { .path-local-treestudyplan .q-header .q-item-heading,
/*.q-line-heading > .q-header-title,*/
}
.path-local-treestudyplan .q-header .q-item-heading.collapsed,
.path-local-treestudyplan .q-header .q-condition-heading, .path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-item-heading.collapsed, .path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading { .path-local-treestudyplan .q-student-results .q-condition-heading {
width: 2rem; width: 2rem;
writing-mode: vertical-lr; writing-mode: vertical-rl;
text-orientation: sideways; text-orientation: sideways;
overflow: hidden; }
text-overflow: ellipsis; .path-local-treestudyplan .q-header .q-line-heading .q-chevron,
.path-local-treestudyplan .q-header .q-item-heading .q-chevron,
.path-local-treestudyplan .q-header .q-condition-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-line-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-item-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-condition-heading .q-chevron {
text-orientation: initial;
writing-mode: horizontal-tb;
} }
.path-local-treestudyplan .q-header .q-period-heading, .path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading, .path-local-treestudyplan .q-header .q-line-heading,
@ -1650,41 +1652,56 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.path-local-treestudyplan .q-student-results .q-condition-heading { .path-local-treestudyplan .q-student-results .q-condition-heading {
vertical-align: top; vertical-align: top;
} }
.path-local-treestudyplan .q-header .q-line-heading, .path-local-treestudyplan .q-header .q-line-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-line-heading { .path-local-treestudyplan .q-student-results .q-line-heading > span.q-wrap {
height: fit-content; display: inline-block;
height: 7rem;
} }
.path-local-treestudyplan .q-header .q-item-heading, .path-local-treestudyplan .q-header .q-item-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-item-heading { .path-local-treestudyplan .q-student-results .q-item-heading > span.q-wrap {
height: 6rem; display: inline-block;
height: 7rem;
white-space: nowrap;
} }
.path-local-treestudyplan .q-header .q-condition-heading, .path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading { .path-local-treestudyplan .q-student-results .q-condition-heading {
text-align: center; text-align: left;
background: var(--conditions-bgcolor);
} }
.path-local-treestudyplan .q-header .q-condition-heading span, .path-local-treestudyplan .q-header .q-condition-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-condition-heading span { .path-local-treestudyplan .q-student-results .q-condition-heading > span.q-wrap {
height: 8rem;
white-space: nowrap;
display: inline-block; display: inline-block;
overflow: hidden; height: 8rem;
text-overflow: ellipsis;
} }
.path-local-treestudyplan .q-result { .path-local-treestudyplan .q-result {
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
border-right: 1px solid gray; border-right: 1px solid var(--border-color);
width: 4rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
background-color: var(--conditions-bgcolor);
}
.path-local-treestudyplan .q-result.overall,
.path-local-treestudyplan .q-result.collapsed {
background-color: var(--courseresult-bgcolor);
} }
.path-local-treestudyplan .q-studentname { .path-local-treestudyplan .q-studentname {
padding: 0.5em; padding: 0.5em;
border-right: 2px solid grey; border-right: 2px solid var(--border-color);
width: 20rem; width: 12rem;
background-color: var(--studentinfo-bgcolor);
} }
.path-local-treestudyplan .q-courseresult i.fa, .path-local-treestudyplan .q-courseresult i.fa,
.path-local-treestudyplan .q-conditionresult i.fa { .path-local-treestudyplan .q-conditionresult i.fa {
font-size: 21px; font-size: 21px;
vertical-align: middle; vertical-align: middle;
} }
.path-local-treestudyplan .q-conditionresult {
font-size: 1.2rem;
display: inline-block;
width: 100%;
}
.path-local-treestudyplan .b-modal-justify-footer-between .modal-footer, .path-local-treestudyplan .b-modal-justify-footer-between .modal-footer,
.features-treestudyplan .b-modal-justify-footer-between .modal-footer { .features-treestudyplan .b-modal-justify-footer-between .modal-footer {

View file

@ -1,24 +1,32 @@
.path-local-treestudyplan { .path-local-treestudyplan {
font: inherit; font: inherit;
--border-color: var(--primary);
--conditions-bgcolor: #e7e7e7;
--courseresult-bgcolor: white;
--studentinfo-bgcolor: white;
table.studyplanreport { table.q-studyplanreport {
table-layout: fixed; table-layout: fixed;
width: calc(12rem + (var(--resultColCount) * 4rem));
} }
.q-header, .q-student-results { .q-header, .q-student-results {
background-color: white; background-color: var(--courseresult-bgcolor);
.q-period-heading, .q-line-heading, .q-item-heading, .q-condition-heading, .q-period-heading, .q-line-heading, .q-item-heading, .q-condition-heading
.q-period-results, .q-line-results, .q-item-results, .q-condition-results
{ {
border-right: 1px solid var(--border-color);
border-top: 1px solid var(--border-color);
padding: 0.5rem;
}
border-right: 1px solid gray; .q-period-heading, .q-line-heading, .q-item-heading,
.q-condition-heading.overall, .q-result.overall {
.q-header-title { background-color: var(--courseresult-bgcolor);
padding: 0.5rem; }
}
.q-period-heading.collapsed {
width:12rem;
} }
.q-result { .q-result {
@ -26,17 +34,16 @@
padding-top: 0.25rem; padding-top: 0.25rem;
} }
} .q-line-heading,
.q-item-heading,
.q-header, .q-student-results {
/*.q-line-heading > .q-header-title,*/
.q-item-heading.collapsed,
.q-condition-heading { .q-condition-heading {
width: 2rem; width: 2rem;
writing-mode: vertical-lr; writing-mode: vertical-rl;
text-orientation: sideways; text-orientation: sideways;
overflow: hidden; .q-chevron {
text-overflow: ellipsis; text-orientation: initial;
writing-mode: horizontal-tb;
}
} }
.q-period-heading, .q-period-heading,
@ -46,22 +53,22 @@
vertical-align: top; vertical-align: top;
} }
.q-line-heading { .q-line-heading > span.q-wrap{
height: fit-content; display:inline-block;
height: 7rem;
} }
.q-item-heading { .q-item-heading > span.q-wrap{
height: 6rem; display:inline-block;
height: 7rem;
white-space: nowrap;
} }
.q-condition-heading { .q-condition-heading {
text-align: center; > span.q-wrap {
span { display:inline-block;
height: 8rem; height: 8rem;
white-space: nowrap;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
} }
text-align: left;
background: var(--conditions-bgcolor);
} }
} }
@ -69,13 +76,21 @@
.q-result { .q-result {
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
border-right: 1px solid gray; border-right: 1px solid var(--border-color);
width: 4rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
background-color: var(--conditions-bgcolor);
&.overall, &.collapsed {
background-color: var(--courseresult-bgcolor);
}
} }
.q-studentname { .q-studentname {
padding: 0.5em; padding: 0.5em;
border-right: 2px solid grey; border-right: 2px solid var(--border-color);
width: 20rem; width: 12rem;
background-color: var(--studentinfo-bgcolor);
} }
.q-courseresult, .q-courseresult,
@ -86,4 +101,11 @@
} }
} }
.q-conditionresult{
font-size: 1.2rem;
display: inline-block;
width: 100%;
}
} }

View file

@ -1577,68 +1577,70 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.path-local-treestudyplan { .path-local-treestudyplan {
font: inherit; font: inherit;
--border-color: var(--primary);
--conditions-bgcolor: #e7e7e7;
--courseresult-bgcolor: white;
--studentinfo-bgcolor: white;
} }
.path-local-treestudyplan table.studyplanreport { .path-local-treestudyplan table.q-studyplanreport {
table-layout: fixed; table-layout: fixed;
width: calc(12rem + var(--resultColCount) * 4rem);
} }
.path-local-treestudyplan .q-header, .path-local-treestudyplan .q-header,
.path-local-treestudyplan .q-student-results { .path-local-treestudyplan .q-student-results {
background-color: white; background-color: var(--courseresult-bgcolor);
} }
.path-local-treestudyplan .q-header .q-period-heading, .path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading, .path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading, .path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading, .path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-header .q-period-results,
.path-local-treestudyplan .q-header .q-line-results,
.path-local-treestudyplan .q-header .q-item-results,
.path-local-treestudyplan .q-header .q-condition-results,
.path-local-treestudyplan .q-student-results .q-period-heading, .path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading, .path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading, .path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading, .path-local-treestudyplan .q-student-results .q-condition-heading {
.path-local-treestudyplan .q-student-results .q-period-results, border-right: 1px solid var(--border-color);
.path-local-treestudyplan .q-student-results .q-line-results, border-top: 1px solid var(--border-color);
.path-local-treestudyplan .q-student-results .q-item-results,
.path-local-treestudyplan .q-student-results .q-condition-results {
border-right: 1px solid gray;
}
.path-local-treestudyplan .q-header .q-period-heading .q-header-title,
.path-local-treestudyplan .q-header .q-line-heading .q-header-title,
.path-local-treestudyplan .q-header .q-item-heading .q-header-title,
.path-local-treestudyplan .q-header .q-condition-heading .q-header-title,
.path-local-treestudyplan .q-header .q-period-results .q-header-title,
.path-local-treestudyplan .q-header .q-line-results .q-header-title,
.path-local-treestudyplan .q-header .q-item-results .q-header-title,
.path-local-treestudyplan .q-header .q-condition-results .q-header-title,
.path-local-treestudyplan .q-student-results .q-period-heading .q-header-title,
.path-local-treestudyplan .q-student-results .q-line-heading .q-header-title,
.path-local-treestudyplan .q-student-results .q-item-heading .q-header-title,
.path-local-treestudyplan .q-student-results .q-condition-heading .q-header-title,
.path-local-treestudyplan .q-student-results .q-period-results .q-header-title,
.path-local-treestudyplan .q-student-results .q-line-results .q-header-title,
.path-local-treestudyplan .q-student-results .q-item-results .q-header-title,
.path-local-treestudyplan .q-student-results .q-condition-results .q-header-title {
padding: 0.5rem; padding: 0.5rem;
} }
.path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-header .q-item-heading,
.path-local-treestudyplan .q-header .q-condition-heading.overall,
.path-local-treestudyplan .q-header .q-result.overall,
.path-local-treestudyplan .q-student-results .q-period-heading,
.path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading.overall,
.path-local-treestudyplan .q-student-results .q-result.overall {
background-color: var(--courseresult-bgcolor);
}
.path-local-treestudyplan .q-header .q-period-heading.collapsed,
.path-local-treestudyplan .q-student-results .q-period-heading.collapsed {
width: 12rem;
}
.path-local-treestudyplan .q-header .q-result, .path-local-treestudyplan .q-header .q-result,
.path-local-treestudyplan .q-student-results .q-result { .path-local-treestudyplan .q-student-results .q-result {
height: 2rem; height: 2rem;
padding-top: 0.25rem; padding-top: 0.25rem;
} }
.path-local-treestudyplan .q-header, .path-local-treestudyplan .q-header .q-line-heading,
.path-local-treestudyplan .q-student-results { .path-local-treestudyplan .q-header .q-item-heading,
/*.q-line-heading > .q-header-title,*/
}
.path-local-treestudyplan .q-header .q-item-heading.collapsed,
.path-local-treestudyplan .q-header .q-condition-heading, .path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-item-heading.collapsed, .path-local-treestudyplan .q-student-results .q-line-heading,
.path-local-treestudyplan .q-student-results .q-item-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading { .path-local-treestudyplan .q-student-results .q-condition-heading {
width: 2rem; width: 2rem;
writing-mode: vertical-lr; writing-mode: vertical-rl;
text-orientation: sideways; text-orientation: sideways;
overflow: hidden; }
text-overflow: ellipsis; .path-local-treestudyplan .q-header .q-line-heading .q-chevron,
.path-local-treestudyplan .q-header .q-item-heading .q-chevron,
.path-local-treestudyplan .q-header .q-condition-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-line-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-item-heading .q-chevron,
.path-local-treestudyplan .q-student-results .q-condition-heading .q-chevron {
text-orientation: initial;
writing-mode: horizontal-tb;
} }
.path-local-treestudyplan .q-header .q-period-heading, .path-local-treestudyplan .q-header .q-period-heading,
.path-local-treestudyplan .q-header .q-line-heading, .path-local-treestudyplan .q-header .q-line-heading,
@ -1650,41 +1652,56 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.path-local-treestudyplan .q-student-results .q-condition-heading { .path-local-treestudyplan .q-student-results .q-condition-heading {
vertical-align: top; vertical-align: top;
} }
.path-local-treestudyplan .q-header .q-line-heading, .path-local-treestudyplan .q-header .q-line-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-line-heading { .path-local-treestudyplan .q-student-results .q-line-heading > span.q-wrap {
height: fit-content; display: inline-block;
height: 7rem;
} }
.path-local-treestudyplan .q-header .q-item-heading, .path-local-treestudyplan .q-header .q-item-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-item-heading { .path-local-treestudyplan .q-student-results .q-item-heading > span.q-wrap {
height: 6rem; display: inline-block;
height: 7rem;
white-space: nowrap;
} }
.path-local-treestudyplan .q-header .q-condition-heading, .path-local-treestudyplan .q-header .q-condition-heading,
.path-local-treestudyplan .q-student-results .q-condition-heading { .path-local-treestudyplan .q-student-results .q-condition-heading {
text-align: center; text-align: left;
background: var(--conditions-bgcolor);
} }
.path-local-treestudyplan .q-header .q-condition-heading span, .path-local-treestudyplan .q-header .q-condition-heading > span.q-wrap,
.path-local-treestudyplan .q-student-results .q-condition-heading span { .path-local-treestudyplan .q-student-results .q-condition-heading > span.q-wrap {
height: 8rem;
white-space: nowrap;
display: inline-block; display: inline-block;
overflow: hidden; height: 8rem;
text-overflow: ellipsis;
} }
.path-local-treestudyplan .q-result { .path-local-treestudyplan .q-result {
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
border-right: 1px solid gray; border-right: 1px solid var(--border-color);
width: 4rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
background-color: var(--conditions-bgcolor);
}
.path-local-treestudyplan .q-result.overall,
.path-local-treestudyplan .q-result.collapsed {
background-color: var(--courseresult-bgcolor);
} }
.path-local-treestudyplan .q-studentname { .path-local-treestudyplan .q-studentname {
padding: 0.5em; padding: 0.5em;
border-right: 2px solid grey; border-right: 2px solid var(--border-color);
width: 20rem; width: 12rem;
background-color: var(--studentinfo-bgcolor);
} }
.path-local-treestudyplan .q-courseresult i.fa, .path-local-treestudyplan .q-courseresult i.fa,
.path-local-treestudyplan .q-conditionresult i.fa { .path-local-treestudyplan .q-conditionresult i.fa {
font-size: 21px; font-size: 21px;
vertical-align: middle; vertical-align: middle;
} }
.path-local-treestudyplan .q-conditionresult {
font-size: 1.2rem;
display: inline-block;
width: 100%;
}
.path-local-treestudyplan .b-modal-justify-footer-between .modal-footer, .path-local-treestudyplan .b-modal-justify-footer-between .modal-footer,
.features-treestudyplan .b-modal-justify-footer-between .modal-footer { .features-treestudyplan .b-modal-justify-footer-between .modal-footer {