- Migrate from "Vue 2.X" to "Vue 3.X"
- Migrate from "Vue Test Utils v1" to "Vue Test Utils v2"
Changes in detail:
- Change `inserted` to `mounted`.
- Change `::v-deep` to `:deep`.
- Change to Vue 3.0 `v-modal` syntax.
- Remove old Vue 2.0 transition name, keep the ones for Vue 3.0.
- Use new global mounting API `createApp`.
- Change `destroy` to `unmount`.
- Bootstrapping:
- Move `provide`s for global dependencies to a bootsrapper from
`App.vue`.
- Remove `productionTip` setting (not in Vue 3).
- Change `IVueBootstrapper` for simplicity and Vue 3 compatible API.
- Add missing tests.
- Remove `.text` access on `VNode` as it's now internal API of Vue.
- Import `CSSProperties` from `vue` instead of `jsx` package.
- Shims:
- Remove unused `shims-tsx.d.ts`.
- Remove `shims-vue.d.ts` that's missing in quickstart template.
- Unit tests:
- Remove old typing workaround for mounting components.
- Rename `propsData` to `props`.
- Remove unneeded `any` cast workarounds.
- Move stubs and `provide`s under `global` object.
Other changes:
- Add `dmg-license` dependency explicitly due to failing electron builds
on macOS (electron-userland/electron-builder#6520,
electron-userland/electron-builder#6489). This was a side-effect of
updating dependencies for this commit.
201 lines
6.2 KiB
Vue
201 lines
6.2 KiB
Vue
<template>
|
|
<SizeObserver
|
|
v-on:sizeChanged="sizeChanged()"
|
|
v-non-collapsing>
|
|
<div
|
|
:id="editorId"
|
|
class="code-area"
|
|
/>
|
|
</SizeObserver>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import {
|
|
defineComponent, onUnmounted, onMounted, inject,
|
|
} from 'vue';
|
|
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
|
import { ICodeChangedEvent } from '@/application/Context/State/Code/Event/ICodeChangedEvent';
|
|
import { IScript } from '@/domain/IScript';
|
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
|
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
|
import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory';
|
|
import SizeObserver from '@/presentation/components/Shared/SizeObserver.vue';
|
|
import { NonCollapsing } from '@/presentation/components/Scripts/View/Cards/NonCollapsingDirective';
|
|
import ace from './ace-importer';
|
|
|
|
export default defineComponent({
|
|
props: {
|
|
theme: {
|
|
type: String,
|
|
default: undefined,
|
|
},
|
|
},
|
|
components: {
|
|
SizeObserver,
|
|
},
|
|
directives: {
|
|
NonCollapsing,
|
|
},
|
|
setup(props) {
|
|
const { onStateChange, currentState } = inject(InjectionKeys.useCollectionState)();
|
|
const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)();
|
|
|
|
const editorId = 'codeEditor';
|
|
let editor: ace.Ace.Editor | undefined;
|
|
let currentMarkerId: number | undefined;
|
|
|
|
onUnmounted(() => {
|
|
destroyEditor();
|
|
});
|
|
|
|
onMounted(() => { // allow editor HTML to render
|
|
onStateChange((newState) => {
|
|
handleNewState(newState);
|
|
}, { immediate: true });
|
|
});
|
|
|
|
function handleNewState(newState: IReadOnlyCategoryCollectionState) {
|
|
destroyEditor();
|
|
editor = initializeEditor(
|
|
props.theme,
|
|
editorId,
|
|
newState.collection.scripting.language,
|
|
);
|
|
const appCode = newState.code;
|
|
updateCode(appCode.current, newState.collection.scripting.language);
|
|
events.unsubscribeAllAndRegister([
|
|
appCode.changed.on((code) => handleCodeChange(code)),
|
|
]);
|
|
}
|
|
|
|
function updateCode(code: string, language: ScriptingLanguage) {
|
|
const innerCode = code || getDefaultCode(language);
|
|
editor.setValue(innerCode, 1);
|
|
}
|
|
|
|
function handleCodeChange(event: ICodeChangedEvent) {
|
|
removeCurrentHighlighting();
|
|
updateCode(event.code, currentState.value.collection.scripting.language);
|
|
if (event.addedScripts?.length > 0) {
|
|
reactToChanges(event, event.addedScripts);
|
|
} else if (event.changedScripts?.length > 0) {
|
|
reactToChanges(event, event.changedScripts);
|
|
}
|
|
}
|
|
|
|
function sizeChanged() {
|
|
editor?.resize();
|
|
}
|
|
|
|
function destroyEditor() {
|
|
editor?.destroy();
|
|
editor = undefined;
|
|
}
|
|
|
|
function removeCurrentHighlighting() {
|
|
if (!currentMarkerId) {
|
|
return;
|
|
}
|
|
editor.session.removeMarker(currentMarkerId);
|
|
currentMarkerId = undefined;
|
|
}
|
|
|
|
function reactToChanges(event: ICodeChangedEvent, scripts: ReadonlyArray<IScript>) {
|
|
const positions = scripts
|
|
.map((script) => event.getScriptPositionInCode(script));
|
|
const start = Math.min(
|
|
...positions.map((position) => position.startLine),
|
|
);
|
|
const end = Math.max(
|
|
...positions.map((position) => position.endLine),
|
|
);
|
|
scrollToLine(end + 2);
|
|
highlight(start, end);
|
|
}
|
|
|
|
function highlight(startRow: number, endRow: number) {
|
|
const AceRange = ace.require('ace/range').Range;
|
|
currentMarkerId = editor.session.addMarker(
|
|
new AceRange(startRow, 0, endRow, 0),
|
|
'code-area__highlight',
|
|
'fullLine',
|
|
);
|
|
}
|
|
|
|
function scrollToLine(row: number) {
|
|
const column = editor.session.getLine(row).length;
|
|
editor.gotoLine(row, column, true);
|
|
}
|
|
|
|
return {
|
|
editorId,
|
|
sizeChanged,
|
|
};
|
|
},
|
|
});
|
|
|
|
function initializeEditor(
|
|
theme: string | undefined,
|
|
editorId: string,
|
|
language: ScriptingLanguage,
|
|
): ace.Ace.Editor {
|
|
theme = theme || 'github';
|
|
const editor = ace.edit(editorId);
|
|
const lang = getLanguage(language);
|
|
editor.getSession().setMode(`ace/mode/${lang}`);
|
|
editor.setTheme(`ace/theme/${theme}`);
|
|
editor.setReadOnly(true);
|
|
editor.setAutoScrollEditorIntoView(true);
|
|
editor.setShowPrintMargin(false); // hides vertical line
|
|
editor.getSession().setUseWrapMode(true); // So code is readable on mobile
|
|
return editor;
|
|
}
|
|
|
|
function getLanguage(language: ScriptingLanguage) {
|
|
switch (language) {
|
|
case ScriptingLanguage.batchfile: return 'batchfile';
|
|
case ScriptingLanguage.shellscript: return 'sh';
|
|
default:
|
|
throw new Error('unknown language');
|
|
}
|
|
}
|
|
|
|
function getDefaultCode(language: ScriptingLanguage): string {
|
|
return new CodeBuilderFactory()
|
|
.create(language)
|
|
.appendCommentLine('privacy.sexy — Now you have the choice.')
|
|
.appendCommentLine(' 🔐 Enforce privacy & security best-practices on Windows, macOS and Linux.')
|
|
.appendLine()
|
|
.appendCommentLine('-- 🤔 How to use')
|
|
.appendCommentLine(' 📙 Start by exploring different categories and choosing different tweaks.')
|
|
.appendCommentLine(' 📙 On top left, you can apply predefined selections for privacy level you\'d like.')
|
|
.appendCommentLine(' 📙 After you choose any tweak, you can download or copy to execute your script.')
|
|
.appendCommentLine(' 📙 Come back regularly to apply latest version for stronger privacy and security.')
|
|
.appendLine()
|
|
.appendCommentLine('-- 🧐 Why privacy.sexy')
|
|
.appendCommentLine(' ✔️ Rich tweak pool to harden security & privacy of the OS and other software on it.')
|
|
.appendCommentLine(' ✔️ No need to run any compiled software on your system, just run the generated scripts.')
|
|
.appendCommentLine(' ✔️ Have full visibility into what the tweaks do as you enable them.')
|
|
.appendCommentLine(' ✔️ Open-source and free (both free as in beer and free as in speech).')
|
|
.toString();
|
|
}
|
|
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
@use "@/presentation/assets/styles/main" as *;
|
|
|
|
:deep() {
|
|
.code-area {
|
|
min-height: 200px;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: auto;
|
|
&__highlight {
|
|
background-color: $color-secondary-light;
|
|
position: absolute;
|
|
}
|
|
}
|
|
}
|
|
</style>
|