diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..ba38abd --- /dev/null +++ b/src/App.vue @@ -0,0 +1,21 @@ + + \ No newline at end of file diff --git a/src/components/Footer.vue b/src/components/Footer.vue new file mode 100644 index 0000000..0b8e6f4 --- /dev/null +++ b/src/components/Footer.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/components/MenuLocales.vue b/src/components/MenuLocales.vue new file mode 100644 index 0000000..65118b3 --- /dev/null +++ b/src/components/MenuLocales.vue @@ -0,0 +1,92 @@ + + diff --git a/src/components/MessageBox.vue b/src/components/MessageBox.vue new file mode 100644 index 0000000..6109597 --- /dev/null +++ b/src/components/MessageBox.vue @@ -0,0 +1,41 @@ + + \ No newline at end of file diff --git a/src/components/MessageBoxView.vue b/src/components/MessageBoxView.vue new file mode 100644 index 0000000..22076cb --- /dev/null +++ b/src/components/MessageBoxView.vue @@ -0,0 +1,191 @@ + + \ No newline at end of file diff --git a/src/components/Modal.vue b/src/components/Modal.vue new file mode 100644 index 0000000..2e9b0c3 --- /dev/null +++ b/src/components/Modal.vue @@ -0,0 +1,166 @@ + + + diff --git a/src/components/NavMenu.vue b/src/components/NavMenu.vue new file mode 100644 index 0000000..e3d90d4 --- /dev/null +++ b/src/components/NavMenu.vue @@ -0,0 +1,307 @@ + + \ No newline at end of file diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue new file mode 100644 index 0000000..96d1b71 --- /dev/null +++ b/src/components/Navbar.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/components/README.md b/src/components/README.md new file mode 100644 index 0000000..ccafa35 --- /dev/null +++ b/src/components/README.md @@ -0,0 +1,9 @@ +## Components + +Components in this dir will be auto-registered and on-demand, powered by [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components). + +### Icons + +You can use icons from almost any icon sets by the power of [Iconify](https://iconify.design/). + +It will only bundle the icons you use. Check out [`unplugin-icons`](https://github.com/antfu/unplugin-icons) for more details. diff --git a/src/components/TiptapEditor.vue b/src/components/TiptapEditor.vue new file mode 100644 index 0000000..021cdcc --- /dev/null +++ b/src/components/TiptapEditor.vue @@ -0,0 +1,212 @@ + + + \ No newline at end of file diff --git a/src/composables/dark.ts b/src/composables/dark.ts new file mode 100644 index 0000000..a2a21dd --- /dev/null +++ b/src/composables/dark.ts @@ -0,0 +1,2 @@ +export const isDark = useDark() +export const toggleDark = useToggle(isDark) diff --git a/src/composables/index.ts b/src/composables/index.ts new file mode 100644 index 0000000..e8d1566 --- /dev/null +++ b/src/composables/index.ts @@ -0,0 +1 @@ +export * from './dark' diff --git a/src/hooks/config.ts b/src/hooks/config.ts new file mode 100644 index 0000000..dcaeb71 --- /dev/null +++ b/src/hooks/config.ts @@ -0,0 +1,110 @@ +// import store from '~/store' +import useState from '~/hooks/useState' +import { show_message, fetch_json } from '~/hooks/utils' +import { MessageType} from '~/typs' +import 'toastify-js/src/toastify.css' +import YAML from 'yaml' + +export const set_config = (res: any) => { + if (res && res.URLS && res.URLS.root) { + Object.keys(res.URLS).forEach(it => { + if (it === 'root' || res.URLS[it].includes('yaml') || res.URLS[it].includes('json') ) + useState().CONFURLS.value[it] = res.URLS[it] + else + useState().CONFURLS.value[it] = res.URLS.root.includes(':') ? `${res.URLS.root}${res.URLS[it]}` : res.URLS[it] + }) + } + if (res && res.ASSETS_PATH) + useState().ASSETS_PATH.value = res.ASSETS_PATH + if (res && res.DATA_PATH) + useState().DATA_PATH.value = res.DATA_PATH + if (res && res.MODEL_ID) + useState().MODEL_ID.value = res.MODEL_ID + if (res && res.URLKEY) + useState().URLKEY.value = res.URLKEY + if (res && res.APPNAME) + useState().APPNAME.value = res.APPNAME + if (res && res.AUTHKEY) + useState().AUTHKEY.value = res.AUTHKEY + if (res && res.AUTH_SEPCHAR) + useState().AUTH_SEPCHAR.value = res.AUTH_SEPCHAR + if (res && res.UUIDONE) + useState().UUIDNONE.value = res.UUIDNONE + if (res && res.TKNLIMIT) + useState().TKNLIMIT.value = res.TKNLIMIT + if (res && res.REFRESHTIME) + useState().REFRESHTIME.value = res.REFRESHTIME + if (res && res.ALLOW_REGISTER) + useState().ALLOW_REGISTER.value = res.ALLOW_REGISTER + if (res && res.RESET_PASSWORD) + useState().RESET_PASSWORD.value = res.RESET_PASSWORD + if (res && res.isDevelmode) + useState().isDevelmode.value = res.isDevelmode + if (res && res.htmlAttrs) + useState().htmlAttrs.value = res.htmlAttrs + if (res && res.dataSections) + useState().dataSections.value = res.dataSections + if (res && res.cv_model) + useState().cv_model.value = res.cv_model +} +export const get_urlpath = async (url: string, mode?: string): Promise<[any, string]> => { + if (url.length == 0) + return [{},'No URL defined'] + let text = '' + let res: any = {} + let error = null + try { + const response = await self.fetch(url, { + method: 'GET', + }) + if (url.includes('.json') || mode === 'json') { + res = await response.json() + } else { + text = await response.text() + } + if (response.ok) { + if (url.includes('.yaml') || mode === 'yaml') { + res = YAML.parse(text) + } else if (!url.includes('.json') && mode !== 'json') { + res = text + } + } else { + error = res.message + } + } + catch (err: any) { + error = err.message + } + return [res,error] +} +export const load_config = async (): Promise<[any, string]> => { + const url = window.CONFIG_LOCATION ? window.CONFIG_LOCATION : '/config.yaml' + // const url = window.CONFIG_LOCATION ? window.CONFIG_LOCATION : 'config.json' + const [res,error] = await get_urlpath(url) + if (error) { + show_message(MessageType.Error, `'Config Load' -> ${error}`, 5000) + useState().connection.value.state = 'connection.error' + return [{},error] + } + return [res, error] +} +export const has_config = async(): Promise => { + let url = useState().CONFURLS.value.root || '' + if (url.length === 0) { + const [res,err] = await load_config() + if (err && err.length > 0) { + return false + } + set_config(res) + url = useState().CONFURLS.value.root || '' + if (url.length === 0) + return false + } + return true +} +export default { + set_config, + load_config, + get_urlpath, + has_config, +} \ No newline at end of file diff --git a/src/hooks/loads.ts b/src/hooks/loads.ts new file mode 100644 index 0000000..a417279 --- /dev/null +++ b/src/hooks/loads.ts @@ -0,0 +1,112 @@ +import useState from '~/hooks/useState' +import { fetch_json } from '~/hooks/utils' +import { load_config } from '~/hooks/config' + +export const store_info = (key:string,res: any) => { + useState().cvdata.value = {} + useState().datalang.value = {} + let models = [] + if (res.models) { + models = res.models + } + Object.keys(res.data).forEach(ky => { + if (ky === 'main') { + useState().cvdata.value = res.data.main + if (res.data.main.models) { + res.data.main.models.forEach((mdl: any) => models.push(mdl)) + } + if (res.data.main.showinfo) { + const showItms = res.data.main.showinfo.filter((it: any) => it.ky === key) + if (showItms[0]) { + useState().showinfo.value = showItms[0] + } else { + const showPub = res.data.main.showinfo.filter((it: any) => it.ky === '') + if (showPub[0]) + useState().showinfo.value = showPub[0] + } + } + } else { + useState().datalang.value[ky] = {} + Object.keys(res.data[ky]).forEach(sec => { + if (res.data[ky][sec]) { + useState().datalang.value[ky][sec] = res.data[ky][sec] + } + }) + } + }) + useState().models.value = models + models.forEach((mdl: any) => useState().selectOps.value[mdl.id] = mdl) +} +export const load_data_model = async(id: string, rootpath: string, ky: string, with_auth: boolean, cllbck: any) => { + if (id && useState().selectOps.value[id]) { + const url = rootpath.includes('http') ? `${rootpath}/${id}` : useState().selectOps.value[id].path.replace('~',rootpath) + const [res,err] = await fetch_json(url, 2000, with_auth) + if (err.length === 0 && res) { + store_info(ky,res) + useState().current_model.value = useState().selectOps.value[id] + useState().current_modelid.value = id + if (cllbck) { + cllbck() + } + } + } +} +export const load_data_info = async (to: any, _from: any, next: any) => { + let url = useState().CONFURLS.value.root || '' + if (url.length > 0) { + const [_res, err]= await load_config() + if (err && err.length > 0) { + next() + return + } + } + url = useState().CONFURLS.value.data || '' + if (url.length == 0) { + next() + return + } + url = `${url}/${useState().MODEL_ID.value}` + const [res,errmsg] = await fetch_json(url, 2000,to.meta.withauth) + // fetch_json(url, 2000,to.meta.withauth,(res,errmsg) => { + if (errmsg.length > 0) { + next() + return + } + if (res && res.data) { + const ky = to.params.ky || to.query.k || '' + store_info(ky,res) + if (useState().selectOps.value[useState().MODEL_ID.value]) { + useState().current_model.value = useState().selectOps.value[useState().MODEL_ID.value] + useState().current_modelid.value = useState().MODEL_ID.value + } + } else { + //next('404') + next() + return + } + next() +} +export const load_currentRoute= async(rt: any): Promise => { + let url = useState().CONFURLS.value.data || '' + if (url.length == 0) { + return false + } + url = `${url}/${useState().MODEL_ID.value}` + const [res,errmsg] = await fetch_json(url, 2000,rt.meta.withauth) + if (errmsg.length === 0 && res && res) { + const ky = rt.params.ky || rt.query.k || '' + store_info(ky,res) + if (useState().models.value[rt.meta.model]) { + useState().current_model.value = useState().models.value[rt.meta.model] + } + useState().current_modelid.value = rt.meta.model + return true + } + return false +} +export default { + store_info, + load_data_model, + load_data_info, + load_currentRoute, +} diff --git a/src/hooks/tracking.ts b/src/hooks/tracking.ts new file mode 100644 index 0000000..f31d113 --- /dev/null +++ b/src/hooks/tracking.ts @@ -0,0 +1,48 @@ +import useState from '~/hooks/useState' +import { MessageType, TrackActionType } from '~/typs' +import { show_message,send_data} from '~/hooks/utils' + +export const track_action = async(e: any, src?: {ref: string,text?: string}, act = 'click') => { + const url = useState().CONFURLS.value.tracking || '' + if (url.length == 0) { + return + } + let href='' + let text='' + if (src && src.ref && src.ref.length > 0 ) { + href=src.ref + text=src.text ? src.text : src.ref + } else if (e && e.target) { + switch(e.target.tagName) { + case 'IMG': + const imgTarget = e.target.parentNode.tagName === 'A' ? e.target.parentNode : null + if (imgTarget && imgTarget.tagName === 'A') { + href = imgTarget.href ? imgTarget.href : '' + text = e.target.alt ? e.target.alt : e.target.src.slice(-1)[0] + } + break + case 'A': + const linkTarget = e.target + if (linkTarget && linkTarget.tagName === 'A') { + href = linkTarget.href ? linkTarget.href : '' + text = linkTarget.innerHTML ? linkTarget.innerHTML : '' + } + break + } + } + const trackAction: TrackActionType = { + when: Date.now().toString(), + where: `${useState().APPNAME.value}>${text}: ${href}`, + what: act, + context: navigator.userAgent, + data: useState().userID.value, + } + const [_res,err] = await send_data(url, trackAction, true, true) + if (err && err.length > 0 ) { + show_message(MessageType.Error, `Error: ${err}`,2000) + } +} + +export default { + track_action, +} diff --git a/src/hooks/useComponent.ts b/src/hooks/useComponent.ts new file mode 100644 index 0000000..c7dc022 --- /dev/null +++ b/src/hooks/useComponent.ts @@ -0,0 +1,97 @@ +import { ref } from 'vue' + +// import WysiwygEditor from '@/WysiwygEditor.vue' +// // import CodeEditor from '@/CodeEditor.vue' +// import GridSettings from '~/views/GridSettings.vue' +// import GridView from '~/views/GridView.vue' +// import TableView from '~/views/TableView.vue' +// import ListView from '~/views/ListView.vue' +// import FormView from '~/views/FormView.vue' + +// import TaFormView from '/app_modules/bm/ta/views/ta_form.vue' +// import TaTableView from '/app_modules/bm/ta/views/ta_table.vue' +// import TaListView from '/app_modules/bm/ta/views/ta_list.vue' + +export enum DynComponent { + // GridSettings, + // GridJs, + // TableView, + // ListView, + // FormView, + // WysiwygEditor, +// CodeEditor, +} + +const asideComponent = ref({}) + +const settingsComponent = ref({}) + +const fullSliderComponent = ref({}) + +const formViewComponent = ref({}) + +const dataViewComponent = ref({}) + +const topPaneComponent = ref({}) + +const bottomPaneComponent = ref({}) + +const moduleComponent = ref({}) + +const getModuleComponent = (key: string, target: string): any => { + // switch (key) { + // case 'ta': + // switch (target) { + // case 'form': + // return TaFormView + // break + // case 'table': + // return TaTableView + // break + // case 'list': + // return TaListView + // break + // } + // break + // } +} +const getComponent = (cmpnt: DynComponent): any => { + // switch (cmpnt) { + // case DynComponent.WysiwygEditor: + // return WysiwygEditor + // break + // // case DynComponent.CodeEditor: + // // return CodeEditor + // // break + // case DynComponent.GridSettings: + // return GridSettings + // break + // case DynComponent.GridJs: + // return GridView + // break + // case DynComponent.TableView: + // return TableView + // break + // case DynComponent.ListView: + // return ListView + // break + // case DynComponent.FormView: + // return FormView + // break + // } +} + +export default function useComponent() { + return { + getModuleComponent, + asideComponent, + settingsComponent, + fullSliderComponent, + dataViewComponent, + formViewComponent, + getComponent, + topPaneComponent, + bottomPaneComponent, + moduleComponent, + } +} diff --git a/src/hooks/useState.ts b/src/hooks/useState.ts new file mode 100644 index 0000000..3607cd6 --- /dev/null +++ b/src/hooks/useState.ts @@ -0,0 +1,189 @@ +import { ref } from 'vue' +import type { SideMenuItemType, UiPanelsType } from '~/typs/cmpnts' +import { DataLangsType, ShowInfoType, HtmlAttrsType, ModelType, ModelSelType, CVDataType } from '~/typs/cv' +import { ConfUrlsType } from '~/typs/config' + +const reqError = ref({ defs: '', lang: '', data: '', gql: '', api: '' }) + +const currentMapKey = ref('') +const isSidebarOpen = ref(false) +const toggleSidebar = () => { + isSidebarOpen.value = !isSidebarOpen.value +} +const showModal = ref(false) + +const isAsidePanelOpen = ref(false) + +const sideSettingButton = ref(false) + +const isSettingsPanelOpen = ref(false) + +const isSearchPanelOpen = ref(false) +const usideSettingButton = ref(false) + +const isNotificationsPanelOpen = ref(false) + +const useSettings = ref(false) + +const useSearch = ref(false) + +const search = ref('') + +const isDevelmode = ref(false) + +const bcPath = ref('') + +const navTitle = ref({ + text: '', + textclick: null as null | Function, + title: '', + cmpnt: '', + ops: [] as any[], + btntype: '', + cllbck: null as null | Function, +}) +const dfltNavTitle = () => { + navTitle.value = { + text: '', + textclick: null as null | Function, + title: '', + cmpnt: '', + ops: [] as any[], + btntype: '', + cllbck: null as null | Function, + } +} + +const bookCllbck = ref() +const bookSelec = (data: string) => { + if (data === '#') { + bcPath.value = '' + dfltNavTitle() + } + else { + if (bookCllbck.value) + bookCllbck.value(data) + // else ... + } +} +const sidebarMenuItems = ref([] as SideMenuItemType[]) + +const checkin = ref(false) +const connection = ref({ + state: '', +}) + +const side_menu_click = ref() +const app_home_click = ref() + +const backdrop_blur = ref(14) +const back_opacity = ref(60) +const panels = ref({} as UiPanelsType) + +const show_profile = ref(false) + +const cvdata = ref({} as CVDataType | any) +const datalang = ref({} as DataLangsType | any) +const showinfo = ref({} as ShowInfoType) +const authinfo = ref({ editable: false, viewchange: false, show: true }) + +const dataSections = ref([] as string[]) +const htmlAttrs = ref({ + bold: '', //'itm-title', + list: 'list-circle ml-5', + link: 'link', + text: '', +} as HtmlAttrsType) + +const cv_model = ref({} as ModelType) +const models = ref([] as ModelType[]) +const selectOps = ref({} as ModelSelType) +const current_model = ref({} as ModelType) +const current_modelid = ref("") + +const APPNAME = ref('') +const AUTHKEY = ref('auth') +const UUIDNONE = ref('none') +const TKNLIMIT = ref(20) +const REFRESHTIME = ref(5) +const ASSETS_PATH = ref("/assets") +const DATA_PATH = ref("/assets/data") +// const DATA_URL = ref("${DATA_PATH}/dist/info.json") +const MODEL_ID = ref("cv") +const URLKEY = ref("?k: ") // location.search.replace('?k: ','') +const AUTH_SEPCHAR = ref(";") +const PASSWD_ENC = ref("enc;") +const ALLOW_REGISTER = ref(false) +const RESET_PASSWORD = ref(false) + +const CONFURLS = ref({} as ConfUrlsType) + +const timeoutAuth = ref(0) +const allowView = ref(false) +const userID = ref('') + +const sourceRoute = ref('') + +export default function useState() { + return { + reqError, + currentMapKey, + isSidebarOpen, + toggleSidebar, + isAsidePanelOpen, + isSettingsPanelOpen, + isSearchPanelOpen, + isNotificationsPanelOpen, + useSettings, + usideSettingButton, + sideSettingButton, + bcPath, + bookSelec, + bookCllbck, + navTitle, + dfltNavTitle, + checkin, + connection, + sidebarMenuItems, + showModal, + side_menu_click, + app_home_click, + useSearch, + search, + back_opacity, + backdrop_blur, + panels, + show_profile, + cvdata, + datalang, + showinfo, + authinfo, + dataSections, + htmlAttrs, + selectOps, + models, + cv_model, + current_model, + current_modelid, + timeoutAuth, + isDevelmode, + allowView, + userID, + sourceRoute, + // APP CONFIG VARS + APPNAME, + AUTHKEY, + UUIDNONE, + TKNLIMIT, + REFRESHTIME, + ASSETS_PATH, + DATA_PATH, + MODEL_ID, + URLKEY, + AUTH_SEPCHAR, + PASSWD_ENC, + ALLOW_REGISTER, + RESET_PASSWORD, + CONFURLS, + } +} diff --git a/src/hooks/utils.ts b/src/hooks/utils.ts new file mode 100644 index 0000000..dad864b --- /dev/null +++ b/src/hooks/utils.ts @@ -0,0 +1,178 @@ +import Toastify from 'toastify-js' +import useState from '~/hooks/useState' +import { checkAuth} from '~/hooks/utilsAuth' +import { MessageType} from '~/typs' +import 'toastify-js/src/toastify.css' + +export const show_message = (typ: MessageType, msg: string, timeout?: number, cllbck?: any): void => { + let cssClass = 'msg-box' + switch (typ) { + case MessageType.Show: + cssClass += ' msg-show' + break + case MessageType.Success: + cssClass += ' msg-ok' + break + case MessageType.Error: + cssClass += ' msg-error' + break + case MessageType.Warning: + cssClass += ' msg-warn' + break + case MessageType.Info: + cssClass += ' msg-info' + break + } + // https://github.com/apvarun/toastify-js + Toastify({ + text: msg, + duration: timeout || 3000, + //destination: '', + //selector: '', + // newWindow: true, + // close: true, + className: cssClass, + offset: { + x: 10, // horizontal axis - can be a number or a string indicating unity. eg: '2em' + y: 50, // vertical axis - can be a number or a string indicating unity. eg: '2em + }, + gravity: 'top', // `top` or `bottom` + position: 'left', // `left`, `center` or `right` + // backgroundColor: 'linear-gradient(to right, #00b09b, #96c93d)', + stopOnFocus: true, // Prevents dismissing of toast on hover + callback: cllbck + // onClick() {}, // Callback after click + }).showToast() +} + +export const translate = (store: any, map: string, ky: string, ctx: string, dflt?: string): string => { + const val = dflt || ky + const lang: any = {} + if (lang && lang.value) + return lang.value[ctx] && lang.value[ctx][ky] ? lang.value[ctx][ky] : val + else if (lang && lang[ctx]) + return lang[ctx][ky] ? lang[ctx][ky] : val + else + return val +} +export const fetch_text = async(path: RequestInfo): Promise => { + try { + const response = await fetch(path) + return !response.ok ? response.text() : new Error('No items found') + } + catch (err) { + return err + } +} +const request_headers = async (with_auth: boolean): Promise<[any, string]> => { + const headers: any = {} // { 'Content-Type': 'application/json' } + headers['Accept']='application/json' + let error= '' + if (with_auth) { + const token = await checkAuth() + if (token && token.length > 0) { + headers['Authorization'] =`Bearer ${token}` + } else { + return [null,`error no auth found`] + } + } + return [headers, error] +} +export const fetch_json = async(path: RequestInfo, timeout = 2000, with_auth = false, cllbck?: any): Promise => { + useState().connection.value.state = '' + let res = null + let error = '' + const showError = (err: string) => { + show_message(MessageType.Error, `'Data Load' -> ${err}`,5000) + useState().connection.value.state = 'connection.error' + } + const [headers,errorHeader] = await request_headers(with_auth) + if (errorHeader.length > 0) { + return [null,errorHeader] + } + try { + const response = await self.fetch(path, { + method: 'GET', + headers, + //mode: 'cors', + }) + res = await response.json() + if (!response.ok && res.message.length > 0) + error=res.message + } + catch (err: any) { + error = err + } + if (error.length > 0) + showError(error) + return [res,error] +} +export const send_data = async(url: string, formData: any, with_auth = true, show = true, cllbck?: any): Promise => { + const [headers,errorHeader] = await request_headers(with_auth) + if (errorHeader.length > 0) { + return [null,errorHeader] + } + headers['Content-Type'] = 'application/json' + let formDataJsonString = '' + try { + formDataJsonString = JSON.stringify(formData) + } + catch (e) { + console.log(e) + return [null,e] + } + try { + const response = await fetch(url, { + method: 'POST', + headers, + body: formDataJsonString, + }) + if (!response.ok && response.status !== 200) { + const errorMessage = await response.json() + console.log(errorMessage) + if (show) + show_message(MessageType.Error, `Send data -> ${errorMessage.error ? errorMessage.error : ''}`) + return [null,errorMessage] + // throw new Error(errorMessage) + } else if (response.ok && response.status === 200) { + const res = await response.json() + if (cllbck) { + cllbck(res) + } + return [res,null] + } + } + catch (e) { + if (show) + show_message(MessageType.Error, `Send data -> ${e}`) + useState().connection.value.state = 'connection.error' + console.log(e) + return [null,e] + } +} +export const run_task = (val: number, task: Function) => { + const now = new Date().getTime() + const timePassed = now % val + const run_at = val - timePassed + setTimeout(task, run_at) +} + +export const parse_str_json_data = (src: string, dflt: object|any):object|any => { + let data = dflt + try { + data = JSON.parse(src) + } + catch (e) { + data = dflt + } + return data +} +export default { + fetch_text, + fetch_json, + send_data, + show_message, + translate, + run_task, + parse_str_json_data, +} diff --git a/src/hooks/utilsAuth.ts b/src/hooks/utilsAuth.ts new file mode 100644 index 0000000..0616650 --- /dev/null +++ b/src/hooks/utilsAuth.ts @@ -0,0 +1,270 @@ +import useState from '~/hooks/useState' +import { MessageType} from '~/typs' +import { show_message } from './utils.js' + +export const check_credentials = async(url: string, data: any): Promise => { + let dataJsonString = '' + try { + dataJsonString = JSON.stringify(data) + } + catch (e) { + console.log(e) + return + } + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: dataJsonString, + }) + if (!response.ok) { + const errorMessage = await response.text() + // throw new Error(errorMessage) + console.log(errorMessage) + return + } + if (response.ok && response.status === 200) + return response.json() + } + catch (e) { + useState().connection.value.state = 'connection.error' + console.log(e) + } +} +export const parseJwt = (token: string|null): any => { + let res = {} + if (token && token.length === 0 || token === "?") { + return res + } + if (token) { + try { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const jsonPayload = decodeURIComponent(atob(base64).split('').map((c) => + '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + ).join('')); + res = JSON.parse(jsonPayload); + } catch(e) { + console.log(e) + localStorage.removeItem(useState().AUTHKEY.value) + } + } + return res +} +export const authExpired = (): boolean => { + const payload = parseJwt(localStorage.getItem(useState().AUTHKEY.value)) + if (payload.exp) { + const now = Date.now() / 1000; + return payload.exp - now < 0 + } else { + return true + } +} +export const onTimeoutAuth = () => { + if (useState().timeoutAuth.value > 0) { + if (useState().isDevelmode.value) + console.log(`timeout already set to: ${useState().timeoutAuth.value}`) + return + } + const payload = parseJwt(localStorage.getItem(useState().AUTHKEY.value)) + const now = Date.now() / 1000; + if (payload.exp) { + let timeout = Math.round(payload.exp - now) + const limit = useState().TKNLIMIT.value * 60 + timeout = timeout < limit ? timeout - (useState().REFRESHTIME.value * 60) : limit - (useState().REFRESHTIME.value * 60) + timeout = timeout < 0 ? 100 : timeout + useState().timeoutAuth.value = timeout + if (useState().isDevelmode.value) + console.log(`AUTH timeout(${limit}): ${timeout} [${Math.round(payload.exp - now)}]`) + setTimeout(() => { + const r = async() => { + useState().timeoutAuth.value=0 + await refreshAuth() + } + r() + }, (timeout*1000)) + } +} +export const refreshAuth = async(val = 0) => { + const url = useState().CONFURLS.value.refreshauth || '' + if (url.length == 0) { + console.log('URL not found for refreshToken') + return + } + let res: any = '', error: any = '' + const headers: any = {} // { 'Content-Type': 'application/json' } + headers['Accept']='application/json' + const token = localStorage.getItem(useState().AUTHKEY.value) + if (token && token.length > 0) { + headers['Authorization'] =`Bearer ${token}` + } + try { + const response = await self.fetch(url, { + method: 'GET', + headers, + //mode: 'cors', + }) + res = await response.json() + if (!response.ok && res.message && res.message.length > 0) + error=res.message + } + catch (err: any) { + error = err.message + } + if (res && res.error && res.error.length > 0) { + error = res.error + } + if (error && error.length > 0) { + show_message(MessageType.Error, `'Auth Refresh' -> ${error}`,5000) + useState().connection.value.state = 'connection.error' + setTimeout(() => { + location.href = '/logout' + }, 1000) + return + } + if (res && res[useState().AUTHKEY.value]) { + localStorage.setItem(useState().AUTHKEY.value,res[useState().AUTHKEY.value]) + console.log(`New auth: ${localStorage.getItem(useState().AUTHKEY.value)}`) + onTimeoutAuth() + } +} +export const getAuth = async(urlpath = '',val = 0): Promise<[any,string]> => { + const urlkey = location.search.replace(useState().URLKEY.value,'') + const uuid = urlkey.length > 0 ? urlkey : useState().UUIDNONE.value + const url = urlpath.length > 0 ? urlpath : (useState().CONFURLS.value.auth ? useState().CONFURLS.value.auth.replace('URLKEY',uuid) : '') + if (url.length === 0) { + console.log('URL not found to get Auth') + return [null,''] + } + let res: any = '' + let errmsg: any = '' + try { + const response = await self.fetch(url, { + method: 'GET', + //mode: 'cors', + }) + res = await response.json() + if (!response.ok && res.message.length > 0) + errmsg=res.message + } + catch (e: any) { + errmsg = e.message + } + if (errmsg && errmsg.length > 0) { + show_message(MessageType.Error, `'Auth Data Load' -> ${errmsg}`,5000) + useState().connection.value.state = 'connection.error' + } + if (res && res.auth) { + if (res.auth === "?") { + useState().userID.value = "?" + } else { + useState().userID.value = res.user ? res.user : '' + localStorage.setItem(useState().AUTHKEY.value,res.auth) + onTimeoutAuth() + } + } + return [res,errmsg] +} +export const checkUserAuth = async(val: string): Promise => { + if (val.length === 0) { + return false + } + const urlkey = location.search.replace(useState().URLKEY.value,'') + const uuid = urlkey.length > 0 ? urlkey : useState().UUIDNONE.value + const urlpath = useState().CONFURLS.value.auth ? useState().CONFURLS.value.auth.replace('URLKEY',uuid) : '' + if (urlpath.length === 0) { + console.log('URL not found to check Auth') + return false + } + const psw = btoa(val) + const url = `${urlpath}${useState().AUTH_SEPCHAR.value}${psw}` + const [res,errmsg] = await getAuth(url,0) + if (errmsg.length === 0 && res && res.user) { + useState().allowView.value=true + const payload = parseJwt(localStorage.getItem(useState().AUTHKEY.value)) + if (payload && payload.id && payload.id === res.user) { + return true + } + } + return false +} +export const checkPerms = async(auth: string|null): Promise => { + if (auth && auth.length === 0 || auth === '?') + return false + if (useState().allowView.value) + return true + const payload = parseJwt(auth) + if (payload && payload.uuid && payload.uuid !== useState().UUIDNONE.value) { + useState().allowView.value=false + return true + } + useState().allowView.value=true + return true +} +export const checkAuth = async(urlpath = '',val = 0): Promise => { + if (authExpired()) { + const [res,errmsg] = await getAuth(urlpath,val) + if (errmsg.length === 0 && res && res.auth) { + return await checkPerms(res.auth) ? res.auth : '' + } + return '' + } + if (location.pathname === '/' && useState().URLKEY.value.length > 0) { + const urlkey = location.search.replace(useState().URLKEY.value,'') + const uuid = urlkey.length > 0 ? urlkey : useState().UUIDNONE.value + const data = authData() + if (data.payload && data.payload.uuid && data.payload.uuid !== uuid) { + const url = useState().CONFURLS.value.auth ? useState().CONFURLS.value.auth.replace('URLKEY',uuid) : '' + if (url.length === 0) { + console.log('URL not found to check Perms') + return '' + } + const [res,_err] = await getAuth(url,val) + if (res.auth) { + return await checkPerms(res.auth) ? res.auth : '' + } + return '' + } else { + onTimeoutAuth() + return await checkPerms(data.auth) ? data.auth : '' + } + } else { + const data = authData() + if (data.payload && data.payload.id) + useState().userID.value = data.payload.id + else + return '' + } + onTimeoutAuth() + const res: string|null = localStorage.getItem(useState().AUTHKEY.value) + return await checkPerms(res) ? res : '' +} +export const authData = ():any => { + const auth = localStorage.getItem(useState().AUTHKEY.value) || '' + const uidefs = {} + const payload = parseJwt(localStorage.getItem(useState().AUTHKEY.value)) + return {auth, payload, uidefs} +} +export const is_logged_user = (): boolean => { + const urlkey = location.search.replace(useState().URLKEY.value, '') + if (urlkey.length > 0) { + if (authExpired()) { + return false + } + return true + } + return false +} +export default { + check_credentials, + parseJwt, + authExpired, + checkAuth, + checkUserAuth, + refreshAuth, + getAuth, + authData, + is_logged_user, +} diff --git a/src/layouts/Default.vue b/src/layouts/Default.vue new file mode 100644 index 0000000..0663a3c --- /dev/null +++ b/src/layouts/Default.vue @@ -0,0 +1,11 @@ + diff --git a/src/layouts/SimpleLayout.vue b/src/layouts/SimpleLayout.vue new file mode 100644 index 0000000..0406436 --- /dev/null +++ b/src/layouts/SimpleLayout.vue @@ -0,0 +1,8 @@ + + + diff --git a/src/logics/dark.ts b/src/logics/dark.ts new file mode 100644 index 0000000..0bc9bd1 --- /dev/null +++ b/src/logics/dark.ts @@ -0,0 +1,25 @@ +import { watch, computed } from 'vue' +import { usePreferredDark, useToggle } from '@vueuse/core' +import { colorSchema } from './store' + +const preferredDark = usePreferredDark() + +export const isDark = computed({ + get() { + return colorSchema.value === 'auto' ? preferredDark.value : colorSchema.value === 'dark' + }, + set(v: boolean) { + if (v === preferredDark.value) + colorSchema.value = 'auto' + else + colorSchema.value = v ? 'dark' : 'light' + }, +}) + +export const toggleDark = useToggle(isDark) + +watch( + isDark, + v => typeof document !== 'undefined' && document.documentElement.classList.toggle('dark', v), + { immediate: true }, +) diff --git a/src/logics/index.ts b/src/logics/index.ts new file mode 100644 index 0000000..e8d1566 --- /dev/null +++ b/src/logics/index.ts @@ -0,0 +1 @@ +export * from './dark' diff --git a/src/logics/store.ts b/src/logics/store.ts new file mode 100644 index 0000000..7db12c2 --- /dev/null +++ b/src/logics/store.ts @@ -0,0 +1,4 @@ +import { Ref } from 'vue' +import { useStorage } from '@vueuse/core' + +export const colorSchema = useStorage('vueuse-color-scheme', 'auto') as Ref<'auto' | 'dark' | 'light'> diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..826ab44 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,35 @@ +import { createApp } from 'vue' +import App from './App.vue' + +import '@unocss/reset/tailwind.css' +import './styles/main.css' +import 'uno.css' +import { createI18n } from 'vue-i18n' +import { createHead, useHead } from '@vueuse/head' +import { messages } from './modules/i18n' +import VueHighlightJS from 'vue3-highlightjs' +import 'highlight.js/styles/solarized-light.css' + +const app = createApp(App) + +app.use(VueHighlightJS) +import routes from './router' +//import AppLayout from '~/layouts/AppLayout.vue' +import SimpleLayout from '~/layouts/SimpleLayout.vue' + +const i18n = createI18n({ + locale: navigator.language === 'es' ? 'es' : 'en', + legacy: false, + fallbackLocale: ['en'], + fallbackWarn: false, + missing: (locale, key, instance) => { + console.warn(`detect '${key}' key missing in '${locale}'`) + }, + messages, +}) +const head = createHead() +app.use(routes) +app.use(i18n) +app.use(head) +app.component('SimpleLayout', SimpleLayout) +app.mount('#app') diff --git a/src/modules/README.md b/src/modules/README.md new file mode 100644 index 0000000..adda9ef --- /dev/null +++ b/src/modules/README.md @@ -0,0 +1,11 @@ +## Modules + +A custom user module system. Place a `.ts` file with the following template, it will be installed automatically. + +```ts +import { UserModule } from '~/types' + +export const install: UserModule = ({ app, router, isClient }) => { + // do something +} +``` diff --git a/src/modules/i18n.ts b/src/modules/i18n.ts new file mode 100644 index 0000000..f7528a1 --- /dev/null +++ b/src/modules/i18n.ts @@ -0,0 +1,25 @@ +import { createI18n } from 'vue-i18n' +// import { UserModule } from '~/types' + +// import i18n resources +// https://vitejs.dev/guide/features.html#glob-import +export const messages = Object.fromEntries( + Object.entries( + import.meta.globEager('../../locales/*.y(a)?ml')) + .map(([key, value]) => { + const yaml = key.endsWith('.yaml') + return [key.slice(14, yaml ? -5 : -4), value.default] + }), +) +/* +export const install: UserModule = ({ app }) => { + const i18n = createI18n({ + legacy: false, + locale: 'en', + messages, + }) + + app.use(i18n) +} + +*/ \ No newline at end of file diff --git a/src/modules/nprogress.ts b/src/modules/nprogress.ts new file mode 100644 index 0000000..8019b41 --- /dev/null +++ b/src/modules/nprogress.ts @@ -0,0 +1,9 @@ +import NProgress from 'nprogress' +import { UserModule } from '~/types' + +export const install: UserModule = ({ isClient, router }) => { + if (isClient) { + router.beforeEach(() => { NProgress.start() }) + router.afterEach(() => { NProgress.done() }) + } +} diff --git a/src/modules/sw.ts b/src/modules/sw.ts new file mode 100644 index 0000000..fd7f7ca --- /dev/null +++ b/src/modules/sw.ts @@ -0,0 +1,12 @@ +import { UserModule } from '~/types' + +export const install: UserModule = ({ isClient, router }) => { + if (isClient) { + router.isReady().then(async() => { + if (isClient) { + const { registerSW } = await import('virtual:pwa-register') + registerSW({ immediate: true }) + } + }) + } +} diff --git a/src/pages/About.md b/src/pages/About.md new file mode 100644 index 0000000..cb313be --- /dev/null +++ b/src/pages/About.md @@ -0,0 +1,21 @@ +--- +title: About +--- + +
+ + +

About

+
+ +[Vitesse](https://github.com/antfu/vitesse) is an opinionated [Vite](https://github.com/vitejs/vite) starter template made by [@antfu](https://github.com/antfu) for mocking apps swiftly. With **file-based routing**, **components auto importing**, **markdown support**, I18n, PWA and uses **Tailwind** v2 for UI. + +

+// syntax highlighting example
+function vitesse() {
+  const foo = 'bar'
+  console.log(foo)
+}
+
+ +heck out the [GitHub repo](https://github.com/antfu/vitesse) for more details. diff --git a/src/pages/README.md b/src/pages/README.md new file mode 100644 index 0000000..929b7c9 --- /dev/null +++ b/src/pages/README.md @@ -0,0 +1,20 @@ +## File-based Routing + +Routes will be auto-generated for Vue files in this dir with the same file structure. +Check out [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) for more details. + +### Path Aliasing + +`~/` is aliased to `./src/` folder. + +For example, instead of having + +```ts +import { isDark } from '../../../../composables' +``` + +now, you can use + +```ts +import { isDark } from '~/composables' +``` diff --git a/src/pages/[...all].vue b/src/pages/[...all].vue new file mode 100755 index 0000000..241e7e2 --- /dev/null +++ b/src/pages/[...all].vue @@ -0,0 +1,5 @@ + diff --git a/src/pages/base.vue b/src/pages/base.vue new file mode 100644 index 0000000..0260305 --- /dev/null +++ b/src/pages/base.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/pages/hi/[name].vue b/src/pages/hi/[name].vue new file mode 100644 index 0000000..d8e4fcd --- /dev/null +++ b/src/pages/hi/[name].vue @@ -0,0 +1,25 @@ + + + diff --git a/src/router.ts b/src/router.ts new file mode 100644 index 0000000..be53f8e --- /dev/null +++ b/src/router.ts @@ -0,0 +1,56 @@ +// eslint-disable-next-line no-unused-vars +import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' + +// import { defineAsyncComponent } from 'vue' +import generatedRoutes from 'virtual:generated-pages' + +import {has_config} from '~/hooks/config' +import {load_data_info} from '~/hooks/loads' + +import Home from '~/views/Home.vue' +// import About from '~/views/about.md' + +const check_config = async(to: any, _from: any, next: any) => { + if (await has_config()) { + next() + } else { + next('/noconfig') + } +} + +const routes: RouteRecordRaw[] = [ + { + path: '/', + name: 'Home', + component: Home, + meta: { + requireAuth: false, + layout: 'AppLayout', + withauth: false, + }, + beforeEnter: [check_config, load_data_info ] + }, + { + path: '/:ky', + name: 'HomeKey', + component: Home, + meta: { + requireAuth: false, + layout: 'AppLayout', + withauth: false, + }, + beforeEnter: [check_config, load_data_info ] + }, + { + path: '/:catchAll(.*)', + name: '404', + meta: { layout: 'Page404' }, + component: () => import('./views/404.vue'), + }, + ...generatedRoutes, +] +const router: any = createRouter({ + history: createWebHistory(), + routes, +}) +export default router diff --git a/src/shims.d.ts b/src/shims.d.ts new file mode 100644 index 0000000..b8fd73a --- /dev/null +++ b/src/shims.d.ts @@ -0,0 +1,60 @@ +/* eslint-disable import/no-duplicates */ + +declare const ASSETS_PATH: string +declare const DATA_PATH: string +declare interface Window { + // extend the window + ROOT_LOCATION: string + CONFIG_LOCATION: string +} + +// with vite-plugin-md, markdowns can be treat as Vue components +declare module '*.md' { + import { ComponentOptions } from 'vue' + const component: ComponentOptions + export default component +} +/* +declare module '*.vue' { + import Vue from 'vue' + export default Vue +} +*/ +declare const _APP_VERSION: string +/* + * Keep states in the global scope to be reusable across Vue instances. + * + * @see {@link /createGlobalState} + * @param stateFactory A factory function to create the state +*/ +/* +declare function createGlobalState( + stateFactory: () => T +): () => T +*/ +interface EventTarget { + value: EventTarget|null + name: string| null + /** + * Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched. + * + * The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture. + * + * When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET. + * + * When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners. + * + * When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed. + * + * The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture. + */ + addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void + /** + * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. + */ + dispatchEvent(event: Event): boolean + /** + * Removes the event listener in target's event listener list with the same type, callback, and options. + */ + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void +} diff --git a/src/styles/main.css b/src/styles/main.css new file mode 100755 index 0000000..4d8b4e9 --- /dev/null +++ b/src/styles/main.css @@ -0,0 +1,254 @@ +html, +body, +#app { + height: 100%; + margin: 0; + padding: 0; +} + +html.dark { + background: #27272a; /*bg-gray-800*/ + color: #f0f0f0; + currentColor: #f0f0f0; +} +.right-item ul li { + margin-left: 1em; +} +.features li b { + color: #6366F1; /* indigo-600 */ + font-weight: 300; +} +.dark .features li b { + color: #A5B4FC; /* indigo-400 */ +} +li a, link, +.task a,h3 a,.link a { + color: #4F46E5; /*indigo-600*/ + text-decoration: underline; +} +.dark li a,.dark link, +.dark .task a,.dark h3 a,.dark .link a { + color: #A5B4FC; /* indigo-400 */ +} +.project-purpose, .project-name { + width: 100%; + padding: 4px 11px; + border-radius: 20px; + margin-bottom: 8px; + border: 1px solid #A5B4FC; /* indigo-400 */ + background-color: #F3F4F6; /* gray-100 */ + color: #6366F1; /* indigo-600 */ + font-size: 1.02em; + font-weight: 600; +} +.dark .project-purpose, .dark .project-name { + background-color: #374151; /* gray-500 */ + border: 1px solid #9CA3AF; /* gray-400 */ + color: #A5B4FC; /* indigo-400 */ +} +.dark svg { + fill: #f0f0f0; +} +.openbox { + border-radius: 10px; + border: 1px solid #374151; /* gray-500 */ +} +.dark .openbox { + border: 1px solid #F3F4F66; /* gray-100 */ +} +.msg-box { + border-radius: 40px !important; + border: 1px solid #374151; /* indigo-500 */ +} +.dark .msg-box { + border: 1px solid #F3F4F6; /* gray-100 */ +} + .msg-ok { + background: linear-gradient(135deg, #83D475, #2EB62C) !important; +} +.msg-error { + background: linear-gradient(135deg, #D90708, #9E1444) !important; +} +.msg-warn { + background: linear-gradient(135deg, #FDA766, #FD7F2C) !important; +} +.msg-show { + background: linear-gradient(135deg, #4949FF, #0000FF) !important; +} +.msg-info { + background: linear-gradient(135deg, #9E9E9E, #696969) !important; +} +.hljs { + background: none !important; +} +.markdown-body { + margin: 2em; +} +.markdown-body p { + margin-top: 1em;; + font-size: 1em; +} + +.markdown-body pre { + padding: 2em; + background: rgba(243,244,246); +} +.dark .markdown-body pre { + background: rgba(75, 85, 99,var(--tw-bg-opacity)) +} +.markdown-body a { + color: rgba(37, 99, 235); +} +.no-list { list-style-type: none; margin-top: 1em;} +/* +.btn { + @apply px-4 py-1 rounded inline-block + bg-teal-600 text-white cursor-pointer + hover:bg-teal-700 + disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50; +} + +.icon-btn { + @apply inline-block cursor-pointer select-none + opacity-75 transition duration-200 ease-in-out + hover:opacity-100 hover:text-teal-600; + font-size: 0.9em; +} +/* +.btn { + @apply px-4 py-1 rounded inline-block + bg-teal-600 text-white cursor-pointer + hover:bg-teal-700 + disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50; +} + +.icon-btn { + @apply inline-block cursor-pointer select-none + opacity-75 transition duration-200 ease-in-out + hover:opacity-100 hover:text-teal-600; + font-size: 0.9em; +} +*/ +progress { + margin-top: 0.5rem; + width: 100%; + height: 10px; + margin-bottom: 0.4rem; +} + +progress::-webkit-progress-bar { + border-radius: 10px; + background-color: #d1d5db; + box-shadow: 0 2px 6px #555; +} + +progress::-webkit-progress-value { + border-radius: 10px 0 0 10px; + background-image: linear-gradient(36deg, #d1fae5, #818cf8); +} +#dinoeatlife-headline::before { + display: inline-block; + height: 30px; + width: 30px; + position: relative; + bottom: -8px; + content: ""; + background-image: url("/images/assets/dinoeatlife-logo.png"); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + margin-right: 12px; +} + +#chrisko-headline::before { + display: inline-block; + height: 30px; + width: 30px; + position: relative; + bottom: -8px; + content: ""; + background-image: url("/images/assets/chrisko-icon.png"); + background-position: center; + background-size: contain; + margin-right: 12px; + border-radius: 50%; +} + +#mealio-headline::before { + display: inline-block; + height: 30px; + width: 30px; + position: relative; + bottom: -8px; + content: ""; + background-image: url("/images/assets/mealio-icon.png"); + background-position: center; + background-size: contain; + margin-right: 12px; +} + +#headsup-headine::before { + display: inline-block; + height: 30px; + width: 30px; + position: relative; + bottom: -4px; + content: ""; + background-image: url("/images/assets/headsup-logo.svg"); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + margin-right: 12px; +} + +.m-ul { + margin-top: 0 !important; +} + +.m-ul li:first-child { + margin-top: 0 !important; +} +.prose>:first-child { + margin-top: 0 !important; +} +.prose h2 { + font-size: 1.5em; + font-weight: 500; + line-height: 1.3333333; + /* margin-bottom: 1em; */ + /* margin-top: 2em; */ + text-transform: uppercase; +} +#cv-wrapper { + box-shadow: rgba(0, 0, 0, 0.16) 0px 10px 36px 0px, rgba(0, 0, 0, 0.06) 0px 0px 0px 1px; + /* box-shadow: rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px, rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset; */ + /* https://getcssscan.com/css-box-shadow-examples */ +} +div strong, p strong, .itm-title { + color: #6366F1; /* indigo-600 */ + font-weight: 600; +} +.dark div strong, .dark p strong, .dark .itm-title { + color: #A5B4FC; /* indigo-400 */ + font-weight: 600; +} +img.project { + width: 10em; + max-height: 5em; + } +@media print { + * { + line-height: 1em !important; + } + .main-container { max-width: fit-content !important;} + .noprint { + visibility: hidden; + display: none; + margin-top: 1em; + } + #cv-wrapper { margin-top: 0 !important;} + .page-break { display: block; break-before: auto; } +} +/* @media all { +.page-break { display: none; } +} */ \ No newline at end of file diff --git a/src/typs/clouds/index.ts b/src/typs/clouds/index.ts new file mode 100644 index 0000000..270d865 --- /dev/null +++ b/src/typs/clouds/index.ts @@ -0,0 +1,86 @@ +export const enum LanguageType { + en = 'en', + es = 'es', + None = 'None', +} +export interface StatusItemType { + title: string + content: string + lang: LanguageType + datetime: string + isOpen: boolean +} +export interface StatusItemDataType { + [key: string]: any + title: string + content: string + lang: LanguageType + datetime: string +} +export const enum ReqType { + tcp = 'tcp', + https = 'https', + NotSet = 'NotSet', +} +export const enum CriticalType { + yes = 'yes', + cloud = 'cloud', + group = 'group', + ifresized = 'ifresized', + no = 'no', +} +export interface SrvcType { + name: string + path: string + req: ReqType + target: string + liveness: string + critical: CriticalType +} +export interface SrvcInfoType { + [key: string]: any + name: string + info: string + srvc: SrvcType +} +export interface CloudGroupItemType { + [key: string]: any + hostname: string + tsksrvcs: SrvcType[] + appsrvcs: SrvcType[] +} +export interface CloudGroupServcType { + [key: string]: any + hostname: string + name: string + tsksrvcs: SrvcInfoElemType[] + appsrvcs: SrvcInfoElemType[] +} + +export interface CloudDataCheck { + [key: string]: any + name: string + apps: Map> + cloud: Map> + infos: StatusItemType[] +} +export interface CloudOptionType { + name: string + option: number +} +export interface CloudGroupDataType { + [key: string]: CloudGroupItemType[] | any +} +export interface ResCloudDataCheck { + [key: string]: any + name: string + cloud: CloudGroupDataType + apps: CloudGroupDataType + // cloud: CloudGroupSrvcType + // statusentries: StatusItemDataType[] +} +export interface ResCloudDataCheckDefs { + [key: string]: any + check: ResCloudDataCheck[] + defs: any +} \ No newline at end of file diff --git a/src/typs/cmpnts/index.ts b/src/typs/cmpnts/index.ts new file mode 100644 index 0000000..91342c0 --- /dev/null +++ b/src/typs/cmpnts/index.ts @@ -0,0 +1,59 @@ + +export const enum Profile { + Premium, + Basic, + Pro, + NotSet, +} + +export enum Auth { + Allow, + Denied, + NotSet, +} + +export enum Permission { + Read, + Write, + ReadWrite, + NotSet, +} + +export interface DataContext { + profile: Profile + auth: Auth + perms: Permission +} + +export interface AccordionItemType { + title?: string + status: boolean + h: number +} + +export interface SideMenuItemType { + title: string + ctx?: string + mode?: string + name?: string + icon_on?: string + img?: string + name_to?: string + show_to: boolean + path?: string + click?: string + type: NavItemType + pfx?: string + href?: string + label?: string + vif?: string + active?: boolean + cllbck?: Function | null +} + +export interface UiPanelsType { + [key: string]: any + id: string + style: string +} + diff --git a/src/typs/config.ts b/src/typs/config.ts new file mode 100644 index 0000000..744284d --- /dev/null +++ b/src/typs/config.ts @@ -0,0 +1,4 @@ + +export interface ConfUrlsType { + [key: string]: string +} diff --git a/src/typs/cv.ts b/src/typs/cv.ts new file mode 100644 index 0000000..3ab5d4e --- /dev/null +++ b/src/typs/cv.ts @@ -0,0 +1,320 @@ +export interface AuthInfoType { + editable: boolean + viewchange: boolean + show: boolean +} +export interface SkillsType { + id: string + auth: AuthInfoType + title: string + max: number + value: number +} +export interface CertificationType { + id: string + auth: AuthInfoType + title: string + author: string + date: string + link: string + href: string + certid: string +} +export interface SitesType { + id: string + auth: AuthInfoType + title: string + sub: string + link: string + type: string + alt: string + img: string +} +export interface LangsType { + id: string + title: string + mode: string +} +export interface WorkExperienceType { + [key: string]: any + auth: AuthInfoType + date: string + where: string + wheredef: string + location: string + position: string + description: string + tools: string[] + tasks: string[] +} +export interface TalksType { + [key: string]: any + auth: AuthInfoType + date: string + title: string + org: string + location: string + description: string[] +} +export interface TeachingType { + [key: string]: any + auth: AuthInfoType + date: string + title: string + org: string + location: string + description: string[] +} +export interface EducationType { + [key: string]: any + auth: AuthInfoType + date: string + title: string + org: string + location: string + cert: string + description: string[] + tools: string[] +} +export interface ProjectType { + [key: string]: any + auth: AuthInfoType + date: string + name: string + title: string + img: string + site: string + code: string + purpose: string + for: string + position: string + license: string + demo: string + capture: string + description: string + features: string[] + builtwith: string[] +} +export interface ProfileType { + [key: string]: unknown; + auth: AuthInfoType + desc: string +} +export interface OtherType { + [key: string]: unknown; + auth: AuthInfoType + desc: string +} +export interface MissionHowType { + [key: string]: unknown; + auth: AuthInfoType + desc: string +} +export interface DataCoreType { + [key: string]: any + name: string + fullname: string + title1: string + title2: string + imgalt: string + imgsrc: string + email: string + phone: string + address: string + postalcode: string + state: string + city: string + country: string + birthdate: string + status: string + mission: string + mission_how: MissionHowType[] + profile: ProfileType[] + certifications: CertificationType[] + skills: SkillsType[] + infra: SkillsType[] + sites: SitesType[] + langs: LangsType[] +} + +export interface DataLangType { + [key: string]: any + imgalt: string + title1: string + title2: string + country: string + birthdate: string + status: string + mission: string + mission_how: MissionHowType[] + profile: ProfileType[] + certifications: CertificationType[] + work_experiences: WorkExperienceType[] + projects: ProjectType[] + education: EducationType[] + talks: TalksType[] + teaching: TeachingType[] + others: OtherType[] +} +export interface DataLangsType { + [key: string]: DataLangType +} +export interface ShowTalksType { + [key: string]: unknown; + auth: AuthInfoType + date: boolean + title: boolean + org: boolean + location: boolean + description: boolean +} +export interface ShowTeachingType { + [key: string]: unknown; + auth: AuthInfoType + date: boolean + title: boolean + org: boolean + location: boolean + description: boolean +} +export interface ShowWorkExperienceType { + [key: string]: unknown; + auth: AuthInfoType + date: boolean + where: boolean + wheredef: boolean + location: boolean + position: boolean + description: boolean + tools: boolean + tasks: boolean +} +export interface ShowEducationType { + [key: string]: unknown; + auth: AuthInfoType + date: boolean + title: boolean + org: boolean + location: boolean + cert: boolean + description: boolean + tools: boolean +} +export interface ShowProjectType { + [key: string]: unknown; + auth: AuthInfoType + name: boolean + img: boolean + title: boolean + purpose: boolean + site: boolean + code: boolean + date: boolean + for: boolean + position: boolean + license: boolean + demo: boolean + capture: boolean + description: boolean + features: boolean + builtwith: boolean +} +export interface ShowInfoType { + [key: string]: any + id: string + ky: string + auth: AuthInfoType + write: boolean + change: boolean + admin: boolean + // save: boolean + fullname: boolean + personal: boolean + title: boolean + image: boolean + mission: boolean + mission_how: boolean + phone: boolean + address: boolean + status: boolean + birthdate: boolean + sites: boolean + skills: boolean + skills_itms: boolean + infra: boolean + certs: boolean + langs: boolean + profile: boolean + work_experience_itms: boolean + work_experience: ShowWorkExperienceType + project_itms: boolean + projects: ShowProjectType + education_itms: boolean + education: ShowEducationType + talks_itms: boolean + talks: ShowTalksType + teaching_itms: boolean + teaching: ShowTeachingType + others_itms: boolean + others: boolean +} + +export interface HtmlAttrsType { + [key: string]: any + bold: string + list: string + text: string + link: string +} +export interface OptionsSelectorType { + title: string + val: string +} +export interface InputBtnsType { + id: string + title: string + typ: string + show: boolean +} +export enum MessageBoxType { + Save = 'save', + Select = 'select', + Input = 'input', + OneInput = 'oneinput', + NotSet = '', +} +export enum NavPosition { + header = 'header', + footer = 'footer', +} + +export interface ModelType { + [key: string]: any + id: string + title: string + path: string +} +export interface ModelSelType { + [key: string]: ModelType +} +export interface CVDataType { + models: ModelType[] + showinfo: ShowInfoType[] + core: DataCoreType[] + work_experience: WorkExperienceType[] + projects: ProjectType[] + education: EducationType[] + talks: TalksType[] + teaching: TeachingType[] + others: OtherType[] +} +export interface CVLangDataType { + [key: string]: CVDataType +} +export interface CVModelDataType { + [key: string]: CVLangDataType +} +export interface CVPostDataType { + u: string + data: CVModelDataType +} \ No newline at end of file diff --git a/src/typs/index.ts b/src/typs/index.ts new file mode 100644 index 0000000..406a495 --- /dev/null +++ b/src/typs/index.ts @@ -0,0 +1,33 @@ +export enum LocalesLabelModes { + auto = 'auto', + value = 'val', + translation = 'trans', +} + +export enum MessageType { + Show, + Success, + Error, + Warning, + Info, +} +export interface MenuItemType { + id: string + title: string + active: boolean + link: string +} + +export interface DataSections { + [key: string]: string +} + +export enum NavItemType { + router_link = 'router_link', + app_link = 'app_link', + a_blank = 'a_blank', + a_link = 'a_link', + cloud_link = 'cloud_link', + module_label = 'module_label', + separator = 'separator', +} diff --git a/src/views/404.vue b/src/views/404.vue new file mode 100644 index 0000000..0b9eb9a --- /dev/null +++ b/src/views/404.vue @@ -0,0 +1,28 @@ + + + diff --git a/src/views/Home.vue b/src/views/Home.vue new file mode 100644 index 0000000..64086a9 --- /dev/null +++ b/src/views/Home.vue @@ -0,0 +1,584 @@ + + \ No newline at end of file diff --git a/src/views/cv/Education.vue b/src/views/cv/Education.vue new file mode 100644 index 0000000..6f7d490 --- /dev/null +++ b/src/views/cv/Education.vue @@ -0,0 +1,165 @@ + + \ No newline at end of file diff --git a/src/views/cv/InfoPanel.vue b/src/views/cv/InfoPanel.vue new file mode 100644 index 0000000..0f3530a --- /dev/null +++ b/src/views/cv/InfoPanel.vue @@ -0,0 +1,476 @@ + + \ No newline at end of file diff --git a/src/views/cv/Others.vue b/src/views/cv/Others.vue new file mode 100644 index 0000000..7b04e64 --- /dev/null +++ b/src/views/cv/Others.vue @@ -0,0 +1,80 @@ + + \ No newline at end of file diff --git a/src/views/cv/Profile.vue b/src/views/cv/Profile.vue new file mode 100644 index 0000000..504e599 --- /dev/null +++ b/src/views/cv/Profile.vue @@ -0,0 +1,74 @@ + + \ No newline at end of file diff --git a/src/views/cv/Projects.vue b/src/views/cv/Projects.vue new file mode 100644 index 0000000..9dc8cc7 --- /dev/null +++ b/src/views/cv/Projects.vue @@ -0,0 +1,214 @@ + + \ No newline at end of file diff --git a/src/views/cv/Skills.vue b/src/views/cv/Skills.vue new file mode 100644 index 0000000..82b0f59 --- /dev/null +++ b/src/views/cv/Skills.vue @@ -0,0 +1,71 @@ + + \ No newline at end of file diff --git a/src/views/cv/Talks.vue b/src/views/cv/Talks.vue new file mode 100644 index 0000000..75ee9f2 --- /dev/null +++ b/src/views/cv/Talks.vue @@ -0,0 +1,135 @@ + + \ No newline at end of file diff --git a/src/views/cv/Teaching.vue b/src/views/cv/Teaching.vue new file mode 100644 index 0000000..e16e5b0 --- /dev/null +++ b/src/views/cv/Teaching.vue @@ -0,0 +1,135 @@ + + \ No newline at end of file diff --git a/src/views/cv/WorkExperience.vue b/src/views/cv/WorkExperience.vue new file mode 100644 index 0000000..8e36d5f --- /dev/null +++ b/src/views/cv/WorkExperience.vue @@ -0,0 +1,183 @@ + + \ No newline at end of file