presets
This commit is contained in:
parent
e477f8fc91
commit
6db3af59e3
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Field from "@/components/Field.vue";
|
import Field from "@/components/Field.vue";
|
||||||
import {type Field as FieldSpec} from "@/urlmaker/specs";
|
import {type Field as FieldSpec} from "@/urlmaker/specs";
|
||||||
import {validateUrl} from "@/urlmaker/validators.ts";
|
import {validateOr, validatePreset, validateUrl} from "@/urlmaker/validators.ts";
|
||||||
import Btn from "@/components/Btn.vue";
|
import Btn from "@/components/Btn.vue";
|
||||||
import {onMounted, onUnmounted, ref, watch} from "vue";
|
import {onMounted, onUnmounted, ref, watch} from "vue";
|
||||||
import Modal from "@/components/Modal.vue";
|
import Modal from "@/components/Modal.vue";
|
||||||
@ -10,10 +10,10 @@ import Modal from "@/components/Modal.vue";
|
|||||||
const field: FieldSpec = {
|
const field: FieldSpec = {
|
||||||
name: '',
|
name: '',
|
||||||
input_type: 'url',
|
input_type: 'url',
|
||||||
label: 'URL of feed for editing',
|
label: 'URL of feed or preset',
|
||||||
default: '',
|
default: '',
|
||||||
required: true,
|
required: true,
|
||||||
validate: validateUrl,
|
validate: validateOr(validateUrl, validatePreset),
|
||||||
}
|
}
|
||||||
|
|
||||||
const visible = defineModel('visible');
|
const visible = defineModel('visible');
|
||||||
|
|||||||
@ -4,20 +4,23 @@ import {ref, watch} from "vue";
|
|||||||
import Btn from "@/components/Btn.vue";
|
import Btn from "@/components/Btn.vue";
|
||||||
import Copyable from "@/components/Copyable.vue";
|
import Copyable from "@/components/Copyable.vue";
|
||||||
import EditUrlModal from "@/components/EditUrlModal.vue";
|
import EditUrlModal from "@/components/EditUrlModal.vue";
|
||||||
import {decodeUrl, encodeUrl, getScreenshotUrl} from "@/urlmaker";
|
import {decodePreset, decodeUrl, encodePreset, encodeUrl, getScreenshotUrl} from "@/urlmaker";
|
||||||
import {useWizardStore} from "@/stores/wizard.ts";
|
import {useWizardStore} from "@/stores/wizard.ts";
|
||||||
import {debounce} from "es-toolkit";
|
import {debounce} from "es-toolkit";
|
||||||
|
import {validatePreset, validateUrl} from "@/urlmaker/validators.ts";
|
||||||
|
|
||||||
const store = useWizardStore();
|
const store = useWizardStore();
|
||||||
const existingLink = ref("");
|
const existingLink = ref("");
|
||||||
const link = ref("");
|
const resultLink = ref("");
|
||||||
|
const resultPreset = ref("");
|
||||||
const editModalVisible = ref(false);
|
const editModalVisible = ref(false);
|
||||||
|
|
||||||
watch(existingLink, async (value) => {
|
watch(existingLink, async (value) => {
|
||||||
if(!value) return;
|
if(!value) return;
|
||||||
existingLink.value = "";
|
existingLink.value = "";
|
||||||
try {
|
try {
|
||||||
store.updateSpecs(await decodeUrl(value));
|
if(validateUrl(value).ok) store.updateSpecs(await decodeUrl(value));
|
||||||
|
else if (validatePreset(value).ok) store.updateSpecs(await decodePreset(value));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
alert(`Decoding error: ${e}`);
|
alert(`Decoding error: ${e}`);
|
||||||
@ -26,17 +29,18 @@ watch(existingLink, async (value) => {
|
|||||||
|
|
||||||
watch(store.specs, debounce(() => {
|
watch(store.specs, debounce(() => {
|
||||||
if (store.formValid) {
|
if (store.formValid) {
|
||||||
generateLink();
|
generate();
|
||||||
} else {
|
} else {
|
||||||
link.value = "";
|
resultLink.value = "";
|
||||||
}
|
}
|
||||||
}, 100),
|
}, 100),
|
||||||
{immediate: true}
|
{immediate: true}
|
||||||
);
|
);
|
||||||
|
|
||||||
async function generateLink() {
|
async function generate() {
|
||||||
try {
|
try {
|
||||||
link.value = await encodeUrl(store.specs);
|
resultLink.value = await encodeUrl(store.specs);
|
||||||
|
resultPreset.value = await encodePreset(store.specs);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
alert(`Encoding error: ${e}`);
|
alert(`Encoding error: ${e}`);
|
||||||
@ -44,7 +48,9 @@ async function generateLink() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function screenshot() {
|
function screenshot() {
|
||||||
|
if(store.formValid) {
|
||||||
window.open(getScreenshotUrl(store.specs.url));
|
window.open(getScreenshotUrl(store.specs.url));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@ -54,9 +60,12 @@ function screenshot() {
|
|||||||
<SpecsForm class="specs-form"></SpecsForm>
|
<SpecsForm class="specs-form"></SpecsForm>
|
||||||
<!-- <Btn :active="store.formValid" @click="generateLink">Generate link</Btn>-->
|
<!-- <Btn :active="store.formValid" @click="generateLink">Generate link</Btn>-->
|
||||||
<Btn :active="store.formValid" @click="screenshot">Screenshot</Btn>
|
<Btn :active="store.formValid" @click="screenshot">Screenshot</Btn>
|
||||||
<Btn @click="editModalVisible = true">Edit existing task</Btn>
|
<Btn @click="editModalVisible = true">Edit existing task / import preset</Btn>
|
||||||
<Btn @click="store.reset">Reset Form</Btn>
|
<Btn @click="store.reset">Reset Form</Btn>
|
||||||
<Copyable v-if="link" :contents="link" class="link-view"></Copyable>
|
<div v-if="resultLink" class="link-label">Link for RSS reader:</div>
|
||||||
|
<Copyable v-if="resultLink" :contents="resultLink" class="link-view"></Copyable>
|
||||||
|
<div v-if="resultPreset" class="link-label">Preset for sharing:</div>
|
||||||
|
<Copyable v-if="resultPreset" :contents="resultPreset" class="link-view"></Copyable>
|
||||||
<EditUrlModal v-model:visible="editModalVisible" v-model="existingLink"></EditUrlModal>
|
<EditUrlModal v-model:visible="editModalVisible" v-model="existingLink"></EditUrlModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -66,13 +75,14 @@ div.wrapper {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
padding-bottom: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.specs-form {
|
.specs-form {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-view {
|
.link-label {
|
||||||
margin-top: 15px !important;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type {Specs} from "@/urlmaker/specs.ts";
|
|||||||
const apiBase = import.meta.env.VITE_API_BASE || document.location.origin;
|
const apiBase = import.meta.env.VITE_API_BASE || document.location.origin;
|
||||||
const renderEndpoint = '/api/v1/render/'; // trailing slash
|
const renderEndpoint = '/api/v1/render/'; // trailing slash
|
||||||
const screenshotEndpoint = '/api/v1/screenshot'; // no trailing slash
|
const screenshotEndpoint = '/api/v1/screenshot'; // no trailing slash
|
||||||
|
export const presetPrefix = 'rssalchemy:';
|
||||||
|
|
||||||
export async function decodeUrl(url: string): Promise<Specs> {
|
export async function decodeUrl(url: string): Promise<Specs> {
|
||||||
const splitUrl = url.split(renderEndpoint);
|
const splitUrl = url.split(renderEndpoint);
|
||||||
@ -10,6 +11,18 @@ export async function decodeUrl(url: string): Promise<Specs> {
|
|||||||
throw 'Split failed';
|
throw 'Split failed';
|
||||||
}
|
}
|
||||||
let encodedData = splitUrl[1];
|
let encodedData = splitUrl[1];
|
||||||
|
return decodeSpecsPart(encodedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function decodePreset(preset: string): Promise<Specs> {
|
||||||
|
if(!preset.startsWith(presetPrefix)) {
|
||||||
|
throw 'Invalid preset';
|
||||||
|
}
|
||||||
|
let encodedData = preset.substring(presetPrefix.length);
|
||||||
|
return decodeSpecsPart(encodedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function decodeSpecsPart(encodedData: string): Promise<Specs> {
|
||||||
console.log('Data len=' + encodedData.length);
|
console.log('Data len=' + encodedData.length);
|
||||||
const m = encodedData.match(/(\d*):?([A-Za-z0-9+/=]+)/);
|
const m = encodedData.match(/(\d*):?([A-Za-z0-9+/=]+)/);
|
||||||
if(!m) {
|
if(!m) {
|
||||||
@ -28,12 +41,20 @@ export async function decodeUrl(url: string): Promise<Specs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function encodeUrl(specs: Specs): Promise<string> {
|
export async function encodeUrl(specs: Specs): Promise<string> {
|
||||||
|
return `${apiBase}${renderEndpoint}${await encodeSpecsPart(specs)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function encodePreset(specs: Specs): Promise<string> {
|
||||||
|
return `${presetPrefix}${await encodeSpecsPart(specs)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function encodeSpecsPart(specs: Specs): Promise<string> {
|
||||||
const jsonData = JSON.stringify(specs);
|
const jsonData = JSON.stringify(specs);
|
||||||
const buf = await compress(jsonData);
|
const buf = await compress(jsonData);
|
||||||
const encodedData = b64encode(buf);
|
const encodedData = b64encode(buf);
|
||||||
console.log('Data len=' + encodedData.length);
|
console.log('Data len=' + encodedData.length);
|
||||||
const version = 0;
|
const version = 0;
|
||||||
return `${apiBase}${renderEndpoint}${version}:${encodedData}`
|
return `${version}:${encodedData}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getScreenshotUrl(url: string): string {
|
export function getScreenshotUrl(url: string): string {
|
||||||
@ -55,7 +76,9 @@ async function compress(s: string): Promise<Uint8Array> {
|
|||||||
let byteArray = new TextEncoder().encode(s);
|
let byteArray = new TextEncoder().encode(s);
|
||||||
let cs = new CompressionStream('deflate-raw');
|
let cs = new CompressionStream('deflate-raw');
|
||||||
let writer = cs.writable.getWriter();
|
let writer = cs.writable.getWriter();
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
writer.write(byteArray);
|
writer.write(byteArray);
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
writer.close();
|
writer.close();
|
||||||
let response = new Response(cs.readable);
|
let response = new Response(cs.readable);
|
||||||
return new Uint8Array(await response.arrayBuffer());
|
return new Uint8Array(await response.arrayBuffer());
|
||||||
@ -64,7 +87,9 @@ async function compress(s: string): Promise<Uint8Array> {
|
|||||||
async function decompress(buf: Uint8Array): Promise<string> {
|
async function decompress(buf: Uint8Array): Promise<string> {
|
||||||
let ds = new DecompressionStream('deflate-raw');
|
let ds = new DecompressionStream('deflate-raw');
|
||||||
let writer = ds.writable.getWriter();
|
let writer = ds.writable.getWriter();
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
writer.write(buf);
|
writer.write(buf);
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
writer.close();
|
writer.close();
|
||||||
let response = new Response(ds.readable);
|
let response = new Response(ds.readable);
|
||||||
return response.text();
|
return response.text();
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import {presetPrefix} from "@/urlmaker/index.ts";
|
||||||
|
|
||||||
type validResult = { ok: boolean, error?: string };
|
type validResult = { ok: boolean, error?: string };
|
||||||
export type validator = (v: string) => validResult
|
export type validator = (v: string) => validResult
|
||||||
|
|
||||||
@ -14,6 +16,27 @@ export function validateUrl(s: string): validResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validatePreset(s: string): validResult {
|
||||||
|
if(!s.startsWith(presetPrefix)) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: 'Not a preset'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {ok: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateOr(...validators: validator[]): validator {
|
||||||
|
return function(s: string): validResult {
|
||||||
|
return validators.reduce<validResult>((res, v) => {
|
||||||
|
let r = v(s);
|
||||||
|
if(r.ok) res.ok = true;
|
||||||
|
else res.error += r.error + '; ';
|
||||||
|
return res;
|
||||||
|
}, {ok: false, error: ''});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function validateSelector(s: string): validResult {
|
export function validateSelector(s: string): validResult {
|
||||||
try {
|
try {
|
||||||
document.createDocumentFragment().querySelector(s);
|
document.createDocumentFragment().querySelector(s);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user