@simitgroup/simpleapp-generator 1.3.3-alpha → 1.3.5-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/framework.js +2 -2
  2. package/dist/framework.js.map +1 -1
  3. package/dist/generate.d.ts.map +1 -1
  4. package/dist/generate.js +7 -11
  5. package/dist/generate.js.map +1 -1
  6. package/dist/processors/jsonschemabuilder.d.ts.map +1 -1
  7. package/dist/processors/jsonschemabuilder.js +8 -0
  8. package/dist/processors/jsonschemabuilder.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/framework.ts +2 -2
  11. package/src/generate.ts +15 -12
  12. package/src/processors/jsonschemabuilder.ts +12 -1
  13. package/templates/basic/nest/controller.ts.eta +17 -6
  14. package/templates/basic/nuxt/component.select.vue.eta +35 -0
  15. package/templates/basic/nuxt/pages.form.vue.eta +5 -8
  16. package/templates/basic/nuxt/simpleapp.generate.client.ts.eta +3 -1
  17. package/templates/nest/.gitignore.eta +6 -1
  18. package/templates/nest/src/simpleapp/generate/commons/user.context.ts.eta +10 -9
  19. package/templates/nest/src/simpleapp/generate/controllers/simpleapp.controller.ts.eta +2 -2
  20. package/templates/nest/src/simpleapp/generate/processors/simpleapp.processor.ts.eta +38 -38
  21. package/templates/nest/src/simpleapp/generate/types/simpleapp.type.ts.eta +14 -9
  22. package/templates/nuxt/assets/css/style.css._eta +43 -7
  23. package/templates/nuxt/assets/primevue/passthrough.ts._eta +26 -15
  24. package/templates/nuxt/components/button/ButtonAction.vue._eta +19 -0
  25. package/templates/nuxt/components/button/ButtonDanger.vue._eta +13 -6
  26. package/templates/nuxt/components/button/ButtonDefault.vue._eta +13 -6
  27. package/templates/nuxt/components/button/ButtonMultiple.vue._eta +9 -7
  28. package/templates/nuxt/components/button/ButtonPrimary.vue._eta +13 -6
  29. package/templates/nuxt/components/button/ButtonText.vue._eta +16 -7
  30. package/templates/nuxt/components/button/ButtonWarning.vue._eta +13 -6
  31. package/templates/nuxt/components/calendar/CalendarSmall.vue.eta +83 -69
  32. package/templates/nuxt/components/chart/card.vue._eta +32 -0
  33. package/templates/nuxt/components/event/EventDocumentViewer.vue._eta +44 -21
  34. package/templates/nuxt/components/event/EventNotification.vue._eta +119 -107
  35. package/templates/nuxt/components/header/button/HeaderButtonMenuPicker.vue._eta +46 -38
  36. package/templates/nuxt/components/header/button/HeaderButtonProfile.vue.eta +131 -75
  37. package/templates/nuxt/components/list/ListView.vue.eta +44 -13
  38. package/templates/nuxt/components/mobile/MobileToolbar.vue.eta +13 -8
  39. package/templates/nuxt/components/overlay/OverlayPanelWithToolBar.vue.eta +35 -37
  40. package/templates/nuxt/components/overlay/OverlaySideBarCrud.vue.eta +3 -4
  41. package/templates/nuxt/components/renderer/RendererDate.vue.eta +3 -2
  42. package/templates/nuxt/components/renderer/RendererForeignKey.vue.eta +38 -34
  43. package/templates/nuxt/components/select/SelectTemplate.vue.eta +79 -0
  44. package/templates/nuxt/components/select/readme.md +1 -0
  45. package/templates/nuxt/components/simpleApp/SimpleAppAutocomplete.vue.eta +181 -35
  46. package/templates/nuxt/components/simpleApp/SimpleAppChildrenList.vue.eta +70 -0
  47. package/templates/nuxt/components/simpleApp/SimpleAppDocumentNo.vue.eta +77 -73
  48. package/templates/nuxt/components/simpleApp/SimpleAppFieldContainer.vue.eta +20 -7
  49. package/templates/nuxt/components/simpleApp/SimpleAppForm.vue.eta +113 -111
  50. package/templates/nuxt/components/simpleApp/{SimpleAppFormToolBar.vue.eta → SimpleAppFormToolBar.vue._eta} +126 -65
  51. package/templates/nuxt/components/simpleApp/SimpleAppInput.vue.eta +117 -43
  52. package/templates/nuxt/components/text/TextPrimary.vue._eta +13 -0
  53. package/templates/nuxt/components/user/UserButtonCreateTenant.vue._eta +64 -0
  54. package/templates/nuxt/components/user/UserTenantPicker.vue.eta +81 -70
  55. package/templates/nuxt/composables/date.generate.ts.eta +2 -0
  56. package/templates/nuxt/composables/getDocument.generate.ts.eta +35 -2
  57. package/templates/nuxt/composables/getOpenApi.generate.ts.eta +5 -1
  58. package/templates/nuxt/composables/refreshDocumentList.generate.ts.eta +14 -2
  59. package/templates/nuxt/lang/en.ts.eta +3 -1
  60. package/templates/nuxt/nuxt.config.ts._eta +3 -0
  61. package/templates/nuxt/pages/[xorg]/organization.vue.eta +80 -61
  62. package/templates/nuxt/pages/index.vue._eta +10 -56
  63. package/templates/nuxt/pages/picktenant.vue._eta +19 -0
  64. package/templates/nuxt/plugins/10.simpleapp-event.ts.eta +10 -2
  65. package/templates/nuxt/server/api/[xorg]/[...].ts.eta +9 -10
  66. package/templates/nuxt/types/calendar.ts.eta +2 -0
  67. package/templates/nuxt/types/events.ts.eta +3 -1
  68. package/templates/nuxt/types/simpleappinput.ts.eta +1 -0
  69. package/templates/project/lang/default._json +150 -63
  70. package/tsconfig.tsbuildinfo +1 -1
  71. package/templates/nuxt/components/user/UserButtonCreateTenant.vue.eta +0 -103
@@ -1,37 +1,43 @@
1
-
2
1
  <template>
3
-
4
- <div class="flex flex-row w-full">
5
- <InputText
6
- @focus="setFocus"
7
- :readonly="readonly"
8
- v-model="modelValue"
9
- :placeholder="placeholder"
10
- :pt="pt"
11
- :class=" !pt ? 'flex-1 w-full rounded-lg '+ ( props.readonly?'':'rounded-tr-none rounded-br-none') : ''"
12
- />
13
- <span class="" v-if="!readonly">
14
- <button type="button" @click="toggle" tabindex="-1"
15
- class="'btn btn-primary p-3 dark:border-blue-900/40 rounded-lg rounded-tl-none rounded-bl-none">
16
- <i class="pi pi-angle-down"></i>
17
- </button>
18
- <OverlayPanel ref="op" class="p-4">
19
- <div class="m-4">
20
- <ul>
21
- <li v-for="docno in docFormatlist" class="hover-list-primary p-2" >
22
- <a class="" @click="chooseFormat(docno)">
23
- <span class="pi pi-hashtag mr-2"></span>
24
- <span class="">{{docno.docNoFormatName}}</span>
25
- <span class=" text text-green-600">{{docno.sample}}</span>
26
- </a>
27
- </li>
28
- </ul>
29
- </div>
30
- </OverlayPanel>
31
- </span>
32
- <!-- {{ Object.getOwnPropertyNames(setting) }} -->
2
+ <div class="flex flex-row w-full">
3
+ <InputText
4
+ @focus="setFocus"
5
+ :readonly="readonly"
6
+ v-model="modelValue"
7
+ :placeholder="placeholder"
8
+ :pt="pt"
9
+ :class="
10
+ !pt
11
+ ? 'flex-1 w-full rounded-lg ' +
12
+ (props.readonly ? '' : 'rounded-tr-none rounded-br-none')
13
+ : ''
14
+ "
15
+ />
16
+ <span class="" v-if="!readonly">
17
+ <button v-ripple
18
+ type="button"
19
+ @click="toggle"
20
+ tabindex="-1"
21
+ class="'btn btn-primary p-3 dark:border-blue-900/40 rounded-lg rounded-tl-none rounded-bl-none"
22
+ >
23
+ <i class="pi pi-angle-down"></i>
24
+ </button>
25
+ <OverlayPanel ref="op" class="p-4">
26
+ <div class="m-4">
27
+ <ul>
28
+ <li v-for="docno in docFormatlist" class="hover-list-primary p-2">
29
+ <a class="" @click="chooseFormat(docno)">
30
+ <span class="pi pi-hashtag mr-2"></span>
31
+ <span class="">{{ docno.docNoFormatName }}</span>
32
+ <span class="text text-green-600">{{ docno.sample }}</span>
33
+ </a>
34
+ </li>
35
+ </ul>
33
36
  </div>
34
-
37
+ </OverlayPanel>
38
+ </span>
39
+ <!-- {{ Object.getOwnPropertyNames(setting) }} -->
40
+ </div>
35
41
  </template>
36
42
  <script lang="ts" setup>
37
43
  /**
@@ -40,52 +46,50 @@
40
46
  * last change 2023-10-28
41
47
  * Author: Ks Tan
42
48
  */
43
- import {ForeignKey} from '~/types'
44
- import OverlayPanel from 'primevue/overlaypanel';
45
- import InputText from 'primevue/inputtext';
46
- import {DocNoFormat} from "~/types"
49
+ import { ForeignKey } from "~/types";
50
+ import OverlayPanel from "primevue/overlaypanel";
51
+ import InputText from "primevue/inputtext";
52
+ import { DocNoFormat } from "~/types";
47
53
  const props = defineProps<{
48
- setting:any
49
- readonly?:boolean
50
- pt?:any
51
- }>()
54
+ setting: any;
55
+ readonly?: boolean;
56
+ pt?: any;
57
+ }>();
52
58
  const op = ref();
53
- const placeholder = ref('')
54
- const docFormatlist = ref()
55
- const modelValue = defineModel<string>({required:true})
56
- const docNoFormat = props.setting.document.getReactiveData().value.docNoFormat
57
- const emits = defineEmits(['update:docNoFormat'])
58
-
59
- const documenttype = props.setting.document.doctype
60
-
61
- const toggle = async (event:any) => {
62
- op.value.toggle(event);
63
- }
59
+ const placeholder = ref("");
60
+ const docFormatlist = ref();
61
+ const modelValue = defineModel<string>({ required: true });
62
+ const docNoFormat = props.setting.document.getReactiveData().value.docNoFormat;
63
+ const emits = defineEmits(["update:docNoFormat"]);
64
64
 
65
- const chooseFormat = (item:any) =>{
66
- placeholder.value = item.sample
67
- const f = item
68
- docNoFormat.value = { _id : f._id, label : f.docNoFormatName}
69
- op.value.toggle();
70
- emits('update:docNoFormat',item)
71
- }
65
+ const documenttype = props.setting.document.doctype;
72
66
 
73
- const loadDocFormats = async () =>{
74
- docFormatlist.value= await getDocFormats(documenttype)
75
- if(docFormatlist.value.length>0){
76
- const f = docFormatlist.value[0]
77
- docNoFormat.value = { _id : f._id, label : f.docNoFormatName}
78
- placeholder.value = docFormatlist.value[0].sample
79
- }
80
- }
67
+ const toggle = async (event: any) => {
68
+ op.value.toggle(event);
69
+ };
81
70
 
71
+ const chooseFormat = (item: any) => {
72
+ placeholder.value = item.sample;
73
+ const f = item;
74
+ docNoFormat.value = { _id: f._id, label: f.docNoFormatName };
75
+ op.value.toggle();
76
+ emits("update:docNoFormat", item);
77
+ };
82
78
 
83
- const setFocus = (ev:any) => {
84
- if(!isMobile()) ev.target.select()
85
- }
79
+ const loadDocFormats = async () => {
80
+ docFormatlist.value = await getDocFormats(documenttype);
81
+ if (docFormatlist.value.length > 0) {
82
+ const f = docFormatlist.value[0];
83
+ docNoFormat.value = { _id: f._id, label: f.docNoFormatName };
84
+ placeholder.value = docFormatlist.value[0].sample;
85
+ }
86
+ };
86
87
 
87
- onMounted(()=>{
88
- loadDocFormats()
88
+ const setFocus = (ev: any) => {
89
+ if (!isMobile()) ev.target.select();
90
+ };
89
91
 
90
- })
91
- </script>
92
+ onMounted(() => {
93
+ loadDocFormats();
94
+ });
95
+ </script>
@@ -15,16 +15,21 @@
15
15
  <!-- <div :uuid="uuid" >{{ modelValue }}</div> -->
16
16
  <!-- <div v-if="typeof modelValue =='object' && typeof modelValue['_id']!='undefined' && typeof modelValue['label']!='undefined' && readonly ==true " :uuid="uuid" class="simpleapp-value-readonly">{{ modelValue['label'] }}</div> -->
17
17
  <!-- <div v-else-if="readonly==true" :uuid="uuid" class="simpleapp-value-readonly">{{ modelValue }}</div> -->
18
- <!-- <slot v-else name="default" :uuid="uuid" :error="error"></slot> -->
19
- <slot name="default" :uuid="uuid" :error="error"></slot>
20
-
18
+ <!-- <slot v-else name="default" :uuid="uuid" :error="error"></slot> -->
19
+ <slot name="default" :uuid="uuid" :error="error"></slot>
21
20
 
22
- <small v-if="error" class="text-danger-600 dark:text-danger-400">{{ error }}</small>
21
+ <small v-if="error" class="text-danger-600 dark:text-danger-400">{{
22
+ error
23
+ }}</small>
23
24
  <small v-else class="input-desc">{{ fielddesc }}</small>
24
25
  </div>
25
26
  <div v-else :class="defaultcssclass">
26
- <label class="etext-danger-600 dark:text-danger-400">wrong path in getField()</label>
27
- <div class="text-danger-600 dark:text-danger-400">{{ props.setting.path }}</div>
27
+ <label class="etext-danger-600 dark:text-danger-400"
28
+ >wrong path in getField()</label
29
+ >
30
+ <div class="text-danger-600 dark:text-danger-400">
31
+ {{ props.setting.path }}
32
+ </div>
28
33
  </div>
29
34
  </template>
30
35
  <script setup lang="ts">
@@ -55,6 +60,7 @@ const props = defineProps<{
55
60
  readonly?: boolean;
56
61
  // error?:string,
57
62
  setting: any;
63
+ resetcount: number;
58
64
  }>();
59
65
 
60
66
  const readonly = computed(() => {
@@ -85,7 +91,7 @@ const getLabelClass = () => {
85
91
  class1 += "-mb-4 ml-1 z-10 text-xs dark:text-gray-100";
86
92
  else if (modelValue.value !== undefined)
87
93
  class1 += "-mb-4 ml-1 z-10 text-xs dark:text-gray-100";
88
- else class1 += "-mb-4 ml-1 z-10 text-xs dark:text-gray-100 hidden";
94
+ else class1 += "-mb-4 ml-1 z-10 text-xs dark:text-gray-100 ";
89
95
  return class1;
90
96
  };
91
97
 
@@ -137,4 +143,11 @@ watch(props.setting.errors, (newvalue, oldvalue) => {
137
143
  // console.log("validation result",props.setting.instancepath,)
138
144
  // error.value = newvalue[props.setting.instancepath].message
139
145
  });
146
+
147
+ watch(
148
+ () => props.resetcount,
149
+ () => {
150
+ error.value = "";
151
+ },
152
+ );
140
153
  </script>
@@ -1,131 +1,133 @@
1
1
  <template>
2
- <form class="simpleapp-form" @submit.prevent="true">
3
- <slot name="header"><h3 class="flex flex-col">{{ title }}</h3></slot>
4
- <slot name="default" :data="document.getData()" :getField="getField"></slot>
5
- </form>
2
+ <form class="simpleapp-form" @submit.prevent="true">
3
+ <slot name="default" :data="document.getData()" :getField="getField"></slot>
4
+ </form>
6
5
  </template>
7
- <script setup lang="ts" >
6
+ <script setup lang="ts">
8
7
  /**
9
8
  * This file was automatically generated by simpleapp generator. Every
10
9
  * MODIFICATION OVERRIDE BY GENERATEOR
11
10
  * last change 2023-10-28
12
11
  * Author: Ks Tan
13
12
  */
14
- import jsonpath from 'jsonpath';
15
- import { SimpleAppClient } from '~/simpleapp/generate/clients/SimpleAppClient'
16
- import type { JSONSchema7,JSONSchema7Definition } from 'json-schema';
17
- import * as alldefaults from '~/simpleapp/generate/defaults'
18
- import _, { upperFirst } from 'lodash'
19
- const props = defineProps<{
20
- title?:string,
21
- document: SimpleAppClient<any,any>
22
- readonly?:boolean
23
- }>()
24
- if(!props.document){
25
- throw "undefine SimpleAppForm property 'document'"
26
- }
13
+ import jsonpath from "jsonpath";
14
+ import { SimpleAppClient } from "~/simpleapp/generate/clients/SimpleAppClient";
15
+ import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
16
+ import * as alldefaults from "~/simpleapp/generate/defaults";
17
+ import _, { upperFirst } from "lodash";
18
+ const props = defineProps<{
19
+
20
+ document: SimpleAppClient<any, any>;
21
+ readonly?: boolean;
22
+ }>();
23
+ if (!props.document) {
24
+ throw "undefine SimpleAppForm property 'document'";
25
+ }
27
26
 
28
- const isreadonly = computed(()=> props.readonly ? props.readonly: props.document.isReadOnly())
27
+ const isreadonly = computed(() =>
28
+ props.readonly ? props.readonly : props.document.isReadOnly(),
29
+ );
29
30
 
30
- // const obj = {schema:props.schema,data: props.schema}
31
- const getField = (path:string)=>{
32
- // console.log("simpleform topath",path)
33
- const data = props.document.getData()
34
- const schema = props.document.getSchema() as JSONSchema7 //force type for compatibility
35
- const fieldsetting = getPathObject(schema,path)
36
- // console.log("setting",fieldsetting)
37
-
38
- type defaulttype = typeof alldefaults
39
- type keytype = keyof defaulttype
40
-
41
- const x : keytype = 'Default'+ props.document.getDocName(true) as keytype
42
- const defaultvalue = alldefaults[x](randomUUID())
43
- return {
44
- path: path,
45
- key: _.last(path.split('/')),
46
- instancepath: getInstancePath(schema,path),
47
- fieldsetting: fieldsetting,
48
- modelObject: data,
49
- document: props.document,
50
- apiObj:props.document.getApi(),
51
- modelField: 'email',
52
- isrequired: getIsRequired(schema,path),
53
- errors: props.document.getErrors(),
54
- readonly: isreadonly.value,
55
- defaultValue: defaultvalue
56
- } //as SimpleAppFieldSetting
57
- }
31
+ // const obj = {schema:props.schema,data: props.schema}
32
+ const getField = (path: string) => {
33
+ // console.log("simpleform topath",path)
34
+ const data = props.document.getData();
35
+ const schema = props.document.getSchema() as JSONSchema7; //force type for compatibility
36
+ const fieldsetting = getPathObject(schema, path);
37
+ // console.log("setting",fieldsetting)
38
+
39
+ type defaulttype = typeof alldefaults;
40
+ type keytype = keyof defaulttype;
58
41
 
59
- // "schemaPath": "#/properties/email/format",
42
+ const x: keytype = ("Default" + props.document.getDocName(true)) as keytype;
43
+ const defaultvalue = alldefaults[x](randomUUID());
44
+ return {
45
+ path: path,
46
+ key: _.last(path.split("/")),
47
+ instancepath: getInstancePath(schema, path),
48
+ fieldsetting: fieldsetting,
49
+ modelObject: data,
50
+ document: props.document,
51
+ apiObj: props.document.getApi(),
52
+ modelField: "email",
53
+ isrequired: getIsRequired(schema, path),
54
+ errors: props.document.getErrors(),
55
+ readonly: isreadonly.value,
56
+ defaultValue: defaultvalue,
57
+ }; //as SimpleAppFieldSetting
58
+ };
60
59
 
61
- const getIsRequired=(schema:any,path:string)=>{
62
- if(!path){
63
- console.error('unknown path')
64
- return 'xx'
65
- }
60
+ // "schemaPath": "#/properties/email/format",
66
61
 
67
- try{
68
- let paths = path.replace('#/','').split('/')
69
- const fieldname = paths[paths.length-1]
70
- paths = paths.slice(0, -2);
71
- let tmp = schema
72
- for(let i=0;i<paths.length;i++){
73
- tmp = tmp[paths[i]]
74
-
75
- }
76
- if(Array.isArray(tmp['required']) ) {
77
- const arr:string[] = tmp['required']
78
- return arr.includes (fieldname)
79
- }else{
80
- return false
81
- }
62
+ const getIsRequired = (schema: any, path: string) => {
63
+ if (!path) {
64
+ console.error("unknown path");
65
+ return "xx";
66
+ }
82
67
 
83
- // console.log("get instance path",instancepath)
84
-
85
- // return tmp
86
- }catch(err:any){
87
- console.error(err.message)
88
- }
68
+ try {
69
+ let paths = path.replace("#/", "").split("/");
70
+ const fieldname = paths[paths.length - 1];
71
+ paths = paths.slice(0, -2);
72
+ let tmp = schema;
73
+ for (let i = 0; i < paths.length; i++) {
74
+ tmp = tmp[paths[i]];
89
75
  }
90
- const getInstancePath=(schema:any,path:string)=>{
91
- if(!path){
92
- console.error('unknown path')
93
- return 'yy'
76
+ if (Array.isArray(tmp["required"])) {
77
+ const arr: string[] = tmp["required"];
78
+ return arr.includes(fieldname);
79
+ } else {
80
+ return false;
94
81
  }
95
- try{
96
- let paths = path.replace('#/','').split('/')
97
- let tmp = schema
98
- let instancepath=''
99
- for(let i=0;i<paths.length;i++){
100
- tmp = tmp[paths[i]]
101
- if(tmp['type'] && paths[i] !='items'){
102
- instancepath=instancepath+'/'+paths[i]
103
- }
104
- }
105
82
 
106
- // console.log("get instance path",instancepath)
107
- return instancepath
108
- // return tmp
109
- }catch(err:any){
110
- console.error(err.message)
83
+ // console.log("get instance path",instancepath)
84
+
85
+ // return tmp
86
+ } catch (err: any) {
87
+ console.error(err.message);
88
+ }
89
+ };
90
+ const getInstancePath = (schema: any, path: string) => {
91
+ if (!path) {
92
+ console.error("unknown path");
93
+ return "yy";
94
+ }
95
+ try {
96
+ let paths = path.replace("#/", "").split("/");
97
+ let tmp = schema;
98
+ let instancepath = "";
99
+ for (let i = 0; i < paths.length; i++) {
100
+ tmp = tmp[paths[i]];
101
+ if (tmp["type"] && paths[i] != "items") {
102
+ instancepath = instancepath + "/" + paths[i];
103
+ }
111
104
  }
112
105
 
106
+ // console.log("get instance path",instancepath)
107
+ return instancepath;
108
+ // return tmp
109
+ } catch (err: any) {
110
+ console.error(err.message);
111
+ }
113
112
 
114
- // let paths = path.replace('#/','').split('/')
115
- // return '/'+paths[1]
116
- }
117
- const getPathObject=(schema:JSONSchema7,path:string):JSONSchema7|undefined=>{
118
- // console.log("path",path)
119
- if(!path || !path.includes('#/properties')){
120
- console.error('unknown path')
121
- return undefined
122
- }
123
- try{
124
- const modifiedpath = path.replace('#','$').replaceAll('/','.')
125
- const queryresult = jsonpath.query(schema,modifiedpath)[0]
126
- return queryresult
127
- }catch(err:any){
128
- console.error(err.message)
129
- }
130
- }
131
- </script>
113
+ // let paths = path.replace('#/','').split('/')
114
+ // return '/'+paths[1]
115
+ };
116
+ const getPathObject = (
117
+ schema: JSONSchema7,
118
+ path: string,
119
+ ): JSONSchema7 | undefined => {
120
+ // console.log("path",path)
121
+ if (!path || !path.includes("#/properties")) {
122
+ console.error("unknown path");
123
+ return undefined;
124
+ }
125
+ try {
126
+ const modifiedpath = path.replace("#", "$").replaceAll("/", ".");
127
+ const queryresult = jsonpath.query(schema, modifiedpath)[0];
128
+ return queryresult;
129
+ } catch (err: any) {
130
+ console.error(err.message);
131
+ }
132
+ };
133
+ </script>