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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ID,street,town_ID
1,6 Place des Vosges,1
2,Church Street,2
3,North Street,3
4,King Street,4
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ID,name,dateOfBirth,placeOfBirth,dateOfDeath,placeOfDeath
101,Emily Brontë,1818-07-30,"Thornton, Yorkshire",1848-12-19,"Haworth, Yorkshire"
107,Charlotte Brontë,1818-04-21,"Thornton, Yorkshire",1855-03-31,"Haworth, Yorkshire"
150,Edgar Allen Poe,1809-01-19,"Boston, Massachusetts",1849-10-07,"Baltimore, Maryland"
170,Richard Carpenter,1929-08-14,"King’s Lynn, Norfolk",2012-02-26,"Hertfordshire, England"
ID,name,dateOfBirth,placeOfBirth,dateOfDeath,placeOfDeath,address_ID
10,Victor Hugo,1802-02-26,"Besançon, Franche-Comté",1885-05-22,"Paris, Île-de-France",1
101,Emily Brontë,1818-07-30,"Thornton, Yorkshire",1848-12-19,"Haworth, Yorkshire",2
107,Charlotte Brontë,1818-04-21,"Thornton, Yorkshire",1855-03-31,"Haworth, Yorkshire",2
150,Edgar Allen Poe,1809-01-19,"Boston, Massachusetts",1849-10-07,"Baltimore, Maryland",3
170,Richard Carpenter,1929-08-14,"King’s Lynn, Norfolk",2012-02-26,"Hertfordshire, England",4
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ ID,title,descr,author_ID,stock,price,currency_code,genre_ID
251,The Raven,"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.",150,333,13.13,USD,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
252,Eleonora,"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.",150,555,14,USD,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
271,Catweazle,"Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.",170,22,150,JPY,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
281,Les Misérables,"Les Misérables (French pronunciation: ​[le mizeʁabl]) is a French historical novel by Victor Hugo, first published in 1862, that is considered one of the greatest novels of the 19th century. In the English-speaking world, the novel is usually referred to by its original French title, although it is sometimes translated as The Miserable Ones, The Wretched, or The Poor Ones. The story examines the nature of law and grace, and expounds upon the history of France, the architecture and urban design of Paris, politics, moral philosophy, antimonarchism, justice, religion, and the types and nature of romantic and familial love.",10,33,20.20,EUR,12aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ID,name,zip,country
1,Paris,75000,France
2,Thornton,NN14,UK
3,Boston,02108,USA
4,King’s Lynn,PE30,UK
17 changes: 17 additions & 0 deletions .vitepress/lib/cds-playground/templates/bookshop/db/schema.cds
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ entity Authors {
age = years_between(dateOfBirth, coalesce(dateOfDeath, date( $now )));
}

extend Authors with {
address : Association to Addresses;
}

entity Addresses {
key ID : Integer;
street : String;
town : Association to Towns;
}

entity Towns {
key ID : Integer;
name : String;
zip : String;
country : String;
}

/** Hierarchically organized Code List for Genres */
entity Genres : cuid, sap.common.CodeList {
parent : Association to Genres;
Expand Down
4 changes: 4 additions & 0 deletions .vitepress/lib/code-groups/restoreCodeGroupPreferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@

if (tabs.length === 0) return

// Skip code groups unrelated to the OS/runtime/cloud-runtime dimensions (e.g. file-path
// tabs), otherwise they'd be forced back to their first tab on every re-init.
if (!tabs.some((tab) => getTabDimension(tab))) return // eslint-disable-line no-undef

const selectedTab = getBestTab(tabs, activeTabs) // eslint-disable-line no-undef
const selectedIndex = tabs.indexOf(selectedTab)

Expand Down
12 changes: 12 additions & 0 deletions .vitepress/lib/code-groups/useCodeGroupSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
addActiveTab,
getActiveTabsByDimension,
getBestTab,
getTabDimension,
setActiveTab,
tabsMatch
} from './shared.js'
Expand Down Expand Up @@ -47,6 +48,11 @@ function findCodeGroups(): CodeGroupInfo[] {

function applyPreference(codeGroup: CodeGroupInfo): void {
const { element, tabs } = codeGroup

// Skip code groups unrelated to the OS/runtime/cloud-runtime dimensions (e.g. file-path
// tabs), otherwise they'd be forced back to their first tab on every re-init.
if (!tabs.some((tab) => getTabDimension(tab))) return

const selectedTab = getBestTab(
tabs,
getActiveTabsByDimension((window as any).__CODE_GROUP_ACTIVE_TABS__)
Expand Down Expand Up @@ -88,6 +94,12 @@ function handleDocumentClick(event: Event): void {
const tabLabel = (label.textContent || '').trim()
if (!tabLabel) return

// Only tabs that belong to a recognized dimension (OS/runtime/cloud-runtime) should be
// synced across the page. Otherwise unrelated code groups sharing a "/" path segment
// (e.g. "srv/admin-service.cds" vs. "srv/cat-service.cds") get fuzzy-matched and forced
// into the wrong active tab.
if (!getTabDimension(tabLabel)) return

const clickedRect = label.getBoundingClientRect()

syncTabs(tabLabel)
Expand Down
119 changes: 99 additions & 20 deletions .vitepress/theme/components/cds-playground/LiveCode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
</svg>
<div v-else v-html="play"></div>
</button>
<button v-if="modelTabs.length" class="icon-button model-toggle-btn" @click="toggleModel"
:title="modelVisible ? 'Hide CDS model' : 'Show CDS model'" :aria-pressed="modelVisible">
<svg class="model-icon" viewBox="0 0 24 24" aria-hidden="true">
<ellipse cx="12" cy="5" rx="8" ry="3" />
<path d="M4 5v6a8 3 0 0 0 16 0V5" />
<path d="M4 11v6a8 3 0 0 0 16 0v-6" />
</svg>
</button>
</div>

<div v-if="queryResult" :class="`vp-code-group ${tabs?.some(tab => tab.error) ? 'error' : ''}`">
Expand Down Expand Up @@ -56,15 +64,40 @@
</div>
</div>
</div>

<div v-if="modelVisible" class="vp-code-group model-group">
<div class="tabs">
<template v-for="tab in modelTabs" :key="tab.key">
<input
type="radio"
:id="tab.key"
:checked="selectedModelTabKey === tab.key"
@click.prevent="toggleModelTab(tab.key)"
>
<label :for="tab.key" @click.prevent="toggleModelTab(tab.key)">{{ tab.name }}</label>
</template>
</div>

<div class="blocks">
<div v-for="tab in modelTabs" :key="tab.key" v-show="selectedModelTabKey === tab.key"
:class="`language-${tab.kind} ${selectedModelTabKey === tab.key ? 'active' : ''}`">
<button title="Copy Code" class="copy" @click="copyCode($event, tab.value)"></button>
<span class="lang">{{ tab.kind }}</span>
<span v-html="format(tab, isDark)"></span>
</div>
</div>
</div>

</template>

<script setup>
import { onMounted, ref, useId } from 'vue'
import { computed, onMounted, ref, useId } from 'vue'
import MonacoEditor from './MonacoEditor.vue'
import { useData } from 'vitepress'
import play from '/icons/play.svg?url&raw'
import { runners } from './runners'
import highlighter from './highlighter'
import templates from 'virtual:templates'

const uid = useId()

Expand Down Expand Up @@ -97,6 +130,22 @@ const queryText = ref(props.initialQuery)
const queryResult = ref(null)
const evalStatus = ref(null)

// the model the query runs against, shown on demand so it doesn't clutter the snippet
const modelVisible = ref(false)
const selectedModelTab = ref(null)
const modelTabs = computed(() => (templates.bookshop ?? [])
.filter(file => file.path.endsWith('.cds'))
.sort((f1, f2) => f1.path.localeCompare(f2.path))
.map(file => ({ key: `${uid}-model-${file.path}`, kind: 'cds', name: file.path, value: file.content })))
const selectedModelTabKey = computed(() => selectedModelTab.value ?? modelTabs.value[0]?.key)

function toggleModel() {
modelVisible.value = !modelVisible.value
}

function toggleModelTab(key) {
selectedModelTab.value = key
}

function format({ value, kind }, dark) {
if (!highlighter.getLoadedLanguages().includes(kind)) {
Expand All @@ -109,23 +158,22 @@ function format({ value, kind }, dark) {
}

function formatTabs(result) {
if (result && result.kind && result.value) {
const { kind, name = 'Result', value } = result
return [
{ key: `${uid}-${name}`, kind, name, value }
]
}
else if (Array.isArray(result) && result[0] && result[0].kind && result[0].value) {
return result.map(r => {
const { kind, name = kind, value } = r
return { key: `${uid}-${name}`, kind, name, value }
})
} else {
return [
{ key: `${uid}-Result`, name: 'Result', value: result }
]
}

if (result && result.kind && result.value) {
const { kind, name = 'Result', value } = result
return [
{ key: `${uid}-${name}`, kind, name, value }
]
}
else if (Array.isArray(result) && result[0] && result[0].kind && result[0].value) {
return result.map(r => {
const { kind, name = kind, value } = r
return { key: `${uid}-${name}`, kind, name, value }
})
} else {
return [
{ key: `${uid}-Result`, name: 'Result', value: result }
]
}
}

function toggleTab(key) {
Expand All @@ -135,10 +183,10 @@ function toggleTab(key) {
// monaco inserts non-breaking spaces
// so we override vitepress's default copy behavior
const timeoutIdMap = new WeakMap()
async function copyCode(event) {
async function copyCode(event, text = queryText.value) {
const el = event.target
event.stopPropagation()
await navigator.clipboard.writeText(queryText.value)
await navigator.clipboard.writeText(text)

el.classList.add('copied')
clearTimeout(timeoutIdMap.get(el))
Expand Down Expand Up @@ -341,6 +389,37 @@ onMounted(() => { metaKey.value = /(Mac|iPhone|iPad)/i.test(navigator?.userAgent
margin-top: 8px;
}

/* Model disclosure toggle: same .icon-button base, muted until active/hover */
.model-toggle-btn {
background-color: transparent;
color: var(--vp-c-text-2);
}

.model-toggle-btn:hover, .model-toggle-btn[aria-pressed="true"] {
background-color: var(--vp-c-default-soft);
color: var(--vp-c-brand-1);
}

.model-toggle-btn .model-icon {
width: 1.4em;
height: 1.4em;
fill: none;
stroke: currentColor;
stroke-width: 1.6;
stroke-linecap: round;
stroke-linejoin: round;
}

/* Sits directly below the query/results area, so drop the default vp-code-group top margin */
.model-group {
margin-top: 0;
}

/* .interactive-query's own bottom margin would otherwise leave a gap before it */
.interactive-query:has(+ .model-group) {
margin-bottom: 0;
}


.vp-code-group.error {
border: 1px solid var(--vp-c-danger-2);
Expand Down
Loading
Loading