Upgrade shakapacker, vue; switch to pinia
This commit is contained in:
parent
bb2e29f25c
commit
f246f71aa9
@ -4,4 +4,6 @@ db/*.sqlite*
|
||||
tmp/*.*
|
||||
public/assets
|
||||
public/packs
|
||||
node_modules/
|
||||
node_modules/
|
||||
.yarn
|
||||
.pnp.*
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -35,3 +35,5 @@ yarn-debug.log*
|
||||
/yarn-error.log
|
||||
yarn-debug.log*
|
||||
.yarn-integrity
|
||||
.yarn
|
||||
.pnp.*
|
@ -31,7 +31,7 @@ WORKDIR /parsley
|
||||
COPY Gemfile* ./
|
||||
RUN bundle install
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
COPY package.json.org yarn.lock ./
|
||||
RUN yarn install --production=true --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
3
Gemfile
3
Gemfile
@ -3,10 +3,11 @@ source 'https://rubygems.org'
|
||||
gem 'rails', '7.2.1'
|
||||
gem 'pg', '~> 1.5.8'
|
||||
|
||||
gem 'shakapacker', '6.5.4'
|
||||
gem 'shakapacker', '8.0.2'
|
||||
gem 'bootsnap', '>= 1.1.0', require: false
|
||||
|
||||
gem 'oj', '~> 3.16.6'
|
||||
gem 'csv', '~> 3.3'
|
||||
|
||||
gem 'kaminari', '~> 1.2.2'
|
||||
gem 'unitwise', '~> 2.3.0'
|
||||
|
@ -81,6 +81,7 @@ GEM
|
||||
concurrent-ruby (1.3.4)
|
||||
connection_pool (2.4.1)
|
||||
crass (1.0.6)
|
||||
csv (3.3.0)
|
||||
dalli (3.2.8)
|
||||
database_cleaner (2.0.2)
|
||||
database_cleaner-active_record (>= 2, < 3)
|
||||
@ -176,6 +177,7 @@ GEM
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
ostruct (0.6.0)
|
||||
package_json (0.1.0)
|
||||
parslet (2.0.0)
|
||||
pg (1.5.8)
|
||||
pry (0.14.2)
|
||||
@ -261,8 +263,9 @@ GEM
|
||||
rspec-support (3.13.1)
|
||||
securerandom (0.3.1)
|
||||
semantic_range (3.0.0)
|
||||
shakapacker (6.5.4)
|
||||
shakapacker (8.0.2)
|
||||
activesupport (>= 5.2)
|
||||
package_json
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
@ -296,6 +299,7 @@ PLATFORMS
|
||||
DEPENDENCIES
|
||||
bcrypt (~> 3.1.18)
|
||||
bootsnap (>= 1.1.0)
|
||||
csv (~> 3.3)
|
||||
dalli (~> 3.2.8)
|
||||
database_cleaner (~> 2.0.2)
|
||||
factory_bot_rails (~> 6.4.3)
|
||||
@ -309,7 +313,7 @@ DEPENDENCIES
|
||||
rails-controller-testing
|
||||
redcarpet (~> 3.6.0)
|
||||
rspec-rails (~> 7.0.1)
|
||||
shakapacker (= 6.5.4)
|
||||
shakapacker (= 8.0.2)
|
||||
sqlite3 (~> 2.1.0)
|
||||
tzinfo-data
|
||||
unitwise (~> 2.3.0)
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Dan Elbert
|
||||
Copyright (c) 2024 Dan Elbert
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
2
Procfile
2
Procfile
@ -1,2 +1,2 @@
|
||||
rails: bundle exec rails s -b 0.0.0.0
|
||||
webpacker: bin/webpack-dev-server
|
||||
shakapacker: bin/shakapacker-dev-server
|
||||
|
@ -5,7 +5,7 @@ A self hosted cookbook
|
||||
|
||||
Parsley is released under the MIT License.
|
||||
|
||||
Copyright (C) 2020 Dan Elbert
|
||||
Copyright (C) 2024 Dan Elbert
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -4,13 +4,15 @@
|
||||
<app-navbar></app-navbar>
|
||||
<section id="main" class="">
|
||||
<div class="container">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view v-if="!hasError"></router-view>
|
||||
<div v-else>
|
||||
<h1>Error!</h1>
|
||||
<p>{{error}}</p>
|
||||
</div>
|
||||
</transition>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade" mode="out-in">
|
||||
<component v-if="!hasError" :is="Component" />
|
||||
<div v-else>
|
||||
<h1>Error!</h1>
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@ -18,31 +20,31 @@
|
||||
|
||||
<script>
|
||||
|
||||
import { mapMutations, mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import { mapState } from "pinia";
|
||||
import TWEEN from '@tweenjs/tween.js';
|
||||
import { useAppConfigStore } from "../stores/appConfig";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
api: api
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
hasError: state => state.error !== null,
|
||||
error: state => state.error,
|
||||
authChecked: state => state.authChecked,
|
||||
initialLoad: state => state.initialLoad
|
||||
...mapState(useAppConfigStore, {
|
||||
hasError: store => store.error !== null,
|
||||
error: store => store.error,
|
||||
authChecked: store => store.authChecked,
|
||||
initialLoad: store => store.initialLoad
|
||||
})
|
||||
},
|
||||
|
||||
watch: {
|
||||
isLoading(val) {
|
||||
if (val) {
|
||||
this.$Progress.start();
|
||||
// this.$Progress.start();
|
||||
} else {
|
||||
this.$Progress.finish();
|
||||
// this.$Progress.finish();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -33,7 +33,7 @@
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: String,
|
||||
modelValue: String,
|
||||
id: String,
|
||||
placeholder: String,
|
||||
name: String,
|
||||
@ -70,12 +70,12 @@
|
||||
},
|
||||
|
||||
created() {
|
||||
this.rawValue = this.value;
|
||||
this.rawValue = this.modelValue;
|
||||
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(newValue) {
|
||||
modelValue(newValue) {
|
||||
this.rawValue = newValue;
|
||||
}
|
||||
},
|
||||
@ -156,7 +156,7 @@
|
||||
|
||||
this.rawValue = newValue;
|
||||
|
||||
this.$emit("input", newValue);
|
||||
this.$emit("update:modelValue", newValue);
|
||||
|
||||
if (newValue.length >= Math.max(1, this.minLength)) {
|
||||
this.debouncedUpdateOptions(newValue);
|
||||
@ -192,7 +192,7 @@
|
||||
|
||||
selectOption(opt) {
|
||||
this.rawValue = this.optionValue(opt);
|
||||
this.$emit("input", this.rawValue);
|
||||
this.$emit("update:modelValue", this.rawValue);
|
||||
this.$emit("optionSelected", opt);
|
||||
this.isListOpen = false;
|
||||
},
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: [Date, String]
|
||||
},
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
computed: {
|
||||
stringValue() {
|
||||
const d = DateTimeUtils.toDate(this.value);
|
||||
const d = DateTimeUtils.toDate(this.modelValue);
|
||||
return DateTimeUtils.formatDateForEdit(d);
|
||||
}
|
||||
},
|
||||
@ -30,7 +30,7 @@
|
||||
methods: {
|
||||
input(val) {
|
||||
let d = DateTimeUtils.toDate(val + " 00:00");
|
||||
this.$emit("input", d);
|
||||
this.$emit("update:modelValue", d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +132,13 @@
|
||||
created() {
|
||||
if (LOADED_APIS[this.svgName] !== true) {
|
||||
for (let sb of this.svgData.scriptBlocks) {
|
||||
new Function(sb)(window);
|
||||
try {
|
||||
new Function(sb)(window);
|
||||
} catch (e) {
|
||||
console.log(sb);
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
}
|
||||
LOADED_APIS[this.svgName] = true;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div ref="container">
|
||||
<div ref="modal" :class="['popup', 'modal', { 'is-wide': wide, 'is-active': open && error === null }]">
|
||||
<Teleport to="body">
|
||||
<div :class="['popup', 'modal', { 'is-wide': wide, 'is-active': open && error === null }]">
|
||||
<div class="modal-background" @click="close"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
@ -15,14 +15,17 @@
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapState } from "vuex";
|
||||
import { mapState } from "pinia";
|
||||
import { useAppConfigStore } from "../stores/appConfig";
|
||||
|
||||
export default {
|
||||
emits: ["dismiss"],
|
||||
|
||||
props: {
|
||||
open: {
|
||||
type: Boolean,
|
||||
@ -35,16 +38,8 @@
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$root.$el.appendChild(this.$refs.modal);
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.$refs.container.appendChild(this.$refs.modal);
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState([
|
||||
...mapState(useAppConfigStore, [
|
||||
'error'
|
||||
])
|
||||
},
|
||||
|
@ -54,7 +54,8 @@
|
||||
<script>
|
||||
|
||||
import UserLogin from "./UserLogin";
|
||||
import { mapState } from "vuex";
|
||||
import { mapState } from "pinia";
|
||||
import { useAppConfigStore } from "../stores/appConfig";
|
||||
import { swUpdate } from "../lib/ServiceWorker";
|
||||
|
||||
export default {
|
||||
@ -65,7 +66,7 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState([
|
||||
...mapState(useAppConfigStore, [
|
||||
'route',
|
||||
'user',
|
||||
'updateAvailable'
|
||||
|
@ -32,7 +32,7 @@
|
||||
default: 0.5
|
||||
},
|
||||
|
||||
value: {
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 0
|
||||
@ -47,7 +47,7 @@
|
||||
|
||||
computed: {
|
||||
ratingPercent() {
|
||||
return ((this.value || 0) / this.starCount) * 100.0;
|
||||
return ((this.modelValue || 0) / this.starCount) * 100.0;
|
||||
},
|
||||
|
||||
temporaryPercent() {
|
||||
@ -69,7 +69,7 @@
|
||||
methods: {
|
||||
handleClick(evt) {
|
||||
if (this.temporaryValue !== null) {
|
||||
this.$emit("input", this.temporaryValue);
|
||||
this.$emit("update:modelValue", this.temporaryValue);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -18,7 +18,7 @@ export default {
|
||||
default: ""
|
||||
},
|
||||
|
||||
value: {
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ""
|
||||
@ -37,7 +37,7 @@ export default {
|
||||
|
||||
methods: {
|
||||
triggerInput: debounce(function() {
|
||||
this.$emit("input", this.text);
|
||||
this.$emit("update:modelValue", this.text);
|
||||
}, 250, {leading: false, trailing: true}),
|
||||
|
||||
userUpdateText(text) {
|
||||
@ -55,7 +55,7 @@ export default {
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$watch("value",
|
||||
this.$watch("modelValue",
|
||||
val => this.propUpdateText(val),
|
||||
{
|
||||
immediate: true
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="tag-editor control">
|
||||
<input ref="input" type="text" class="input" :value="tagText" @input="inputHandler" @focus="getFocus" @blur="loseFocus">
|
||||
<div class="tags">
|
||||
<span v-for="t in value" :key="t" class="tag">{{t}}</span>
|
||||
<span v-for="t in modelValue" :key="t" class="tag">{{t}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
modelValue: {
|
||||
required: true,
|
||||
type: Array
|
||||
}
|
||||
@ -25,7 +25,7 @@
|
||||
|
||||
computed: {
|
||||
tagText() {
|
||||
return this.value.join(" ");
|
||||
return this.modelValue.join(" ");
|
||||
}
|
||||
},
|
||||
|
||||
@ -54,8 +54,8 @@
|
||||
|
||||
const newTags = [...new Set(str.toString().split(/\s+/).filter(t => t.length > 0))];
|
||||
|
||||
if (!this.arraysEqual(newTags, this.value)) {
|
||||
this.$emit("input", newTags);
|
||||
if (!this.arraysEqual(newTags, this.modelValue)) {
|
||||
this.$emit("update:modelValue", newTags);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
<div class="field">
|
||||
<label v-if="label.length" class="label is-small-mobile">{{ label }}</label>
|
||||
<div :class="controlClasses">
|
||||
<textarea v-if="isTextarea" :class="inputClasses" :value="value" @input="input" :disabled="disabled"></textarea>
|
||||
<input v-else :type="type" :class="inputClasses" :value="value" @input="input" :disabled="disabled">
|
||||
<textarea v-if="isTextarea" :class="inputClasses" :value="modelValue" @input="input" :disabled="disabled"></textarea>
|
||||
<input v-else :type="type" :class="inputClasses" :value="modelValue" @input="input" :disabled="disabled">
|
||||
<app-icon class="is-right" icon="warning" v-if="validationError !== null"></app-icon>
|
||||
</div>
|
||||
<p v-if="helpMessage !== null" :class="helpClasses">
|
||||
@ -21,7 +21,7 @@
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
value: {
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
@ -83,7 +83,7 @@
|
||||
|
||||
methods: {
|
||||
input(evt) {
|
||||
this.$emit("input", evt.target.value);
|
||||
this.$emit("update:modelValue", evt.target.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,11 +62,14 @@
|
||||
<button class="button" type="button" @click="addUnit">Add Unit</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Grams</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="unit in visibleFoodUnits" :key="unit.id">
|
||||
<td>
|
||||
<div class="control">
|
||||
@ -82,6 +85,7 @@
|
||||
<button type="button" class="button is-danger" @click="removeUnit(unit)">X</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@ -96,14 +100,18 @@
|
||||
<div class="message-body">
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Grams</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="unit in food.ndbn_units">
|
||||
<td>{{unit.description}}</td>
|
||||
<td>{{unit.gram_weight}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
@ -139,7 +147,8 @@
|
||||
<script>
|
||||
|
||||
import api from "../lib/Api";
|
||||
import { mapState } from "vuex";
|
||||
import { mapState } from "pinia";
|
||||
import { useNutrientStore } from "../stores/nutrient";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@ -166,7 +175,7 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
...mapState(useNutrientStore, {
|
||||
nutrients: 'nutrientList'
|
||||
}),
|
||||
|
||||
|
@ -55,7 +55,8 @@
|
||||
|
||||
<script>
|
||||
|
||||
import { mapState } from "vuex";
|
||||
import { mapState } from "pinia";
|
||||
import { useNutrientStore } from "../stores/nutrient";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@ -66,7 +67,7 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
...mapState(useNutrientStore, {
|
||||
nutrients: 'nutrientList'
|
||||
}),
|
||||
}
|
||||
|
@ -71,6 +71,7 @@
|
||||
<th>Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Heading</td>
|
||||
<td>
|
||||
@ -155,6 +156,7 @@ _underline_
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 class="title is-3">Basic Example</h3>
|
||||
@ -206,7 +208,7 @@ _underline_
|
||||
|
||||
<script>
|
||||
|
||||
import autosize from "autosize";
|
||||
//import autosize from "autosize";
|
||||
import debounce from "lodash/debounce";
|
||||
import api from "../lib/Api";
|
||||
|
||||
|
@ -12,18 +12,22 @@
|
||||
</div>
|
||||
<div class="column is-half">
|
||||
<table class="table is-bordered is-narrow is-size-7">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Unit</th>
|
||||
<th>Name</th>
|
||||
<th>Prep</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="i in bulkIngredientPreview">
|
||||
<td>{{i.quantity}}</td>
|
||||
<td>{{i.units}}</td>
|
||||
<td>{{i.name}}</td>
|
||||
<td>{{i.preparation}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,7 +47,8 @@
|
||||
|
||||
import RecipeEditIngredientItem from "./RecipeEditIngredientItem";
|
||||
|
||||
import { mapState } from "vuex";
|
||||
import { mapState } from "pinia";
|
||||
import { useMediaQueryStore } from "../stores/mediaQuery";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@ -61,8 +66,8 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
isMobile: state => state.mediaQueries.mobile
|
||||
...mapState(useMediaQueryStore, {
|
||||
isMobile: store => store.mobile
|
||||
}),
|
||||
bulkIngredientPreview() {
|
||||
if (this.bulkEditText === null) {
|
||||
|
@ -65,14 +65,18 @@
|
||||
<div class="message-header" @click="showNutrition = !showNutrition">Nutrition Data</div>
|
||||
<div class="message-body" v-show="showNutrition">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="nutrient in recipe.nutrition_data.nutrients" :key="nutrient.name">
|
||||
<td>{{nutrient.label}}</td>
|
||||
<td>{{ roundValue(nutrient.value) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 class="title is-5">Nutrition Calculation Warnings</h3>
|
||||
@ -149,7 +153,8 @@
|
||||
<script>
|
||||
|
||||
import api from "../lib/Api";
|
||||
import { mapActions, mapMutations, mapState } from "vuex";
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useTaskStore } from "../stores/task";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@ -185,7 +190,7 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState([
|
||||
...mapState(useTaskStore, [
|
||||
'taskLists'
|
||||
]),
|
||||
|
||||
@ -235,11 +240,8 @@
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions([
|
||||
'ensureTaskLists'
|
||||
]),
|
||||
|
||||
...mapMutations([
|
||||
...mapActions(useTaskStore, [
|
||||
'ensureTaskLists',
|
||||
'setCurrentTaskList'
|
||||
]),
|
||||
|
||||
|
@ -63,8 +63,8 @@
|
||||
<script>
|
||||
|
||||
import * as Errors from '../lib/Errors';
|
||||
import { mapActions } from "vuex";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import { mapActions } from "pinia";
|
||||
import { useTaskStore } from "../stores/task";
|
||||
|
||||
import TaskItemEdit from "./TaskItemEdit";
|
||||
|
||||
@ -115,7 +115,7 @@
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions([
|
||||
...mapActions(useTaskStore, [
|
||||
'createTaskItem',
|
||||
'updateTaskItem',
|
||||
'deleteTaskItems',
|
||||
|
@ -53,19 +53,20 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@import "~styles/variables";
|
||||
@use "bulma/sass" as bulma;
|
||||
@use 'sass:color';
|
||||
|
||||
div.dropdown-item {
|
||||
cursor: pointer;
|
||||
|
||||
&.hovered {
|
||||
color: $black;
|
||||
background-color: $background;
|
||||
color: bulma.$black;
|
||||
background-color: bulma.$background;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: $link-invert;
|
||||
background-color: $link;
|
||||
color: color.invert(bulma.$link);
|
||||
background-color: bulma.$link;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Parsley is released under the MIT License. All code © Dan Elbert 2020.
|
||||
Parsley is released under the MIT License. All code © Dan Elbert 2024.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -15,7 +15,6 @@
|
||||
<script>
|
||||
|
||||
import FoodShow from "./FoodShow";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
|
||||
export default {
|
||||
@ -26,9 +25,9 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
foodId: state => state.route.params.id,
|
||||
})
|
||||
foodId() {
|
||||
return this.$route.params.id;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
|
@ -12,7 +12,6 @@
|
||||
<script>
|
||||
|
||||
import FoodEdit from "./FoodEdit";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from '../lib/Errors';
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
<script>
|
||||
|
||||
import FoodEdit from "./FoodEdit";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from '../lib/Errors';
|
||||
|
||||
@ -30,9 +29,9 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
foodId: state => state.route.params.id,
|
||||
})
|
||||
foodId() {
|
||||
return this.$route.params.id;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -17,7 +17,6 @@
|
||||
<script>
|
||||
|
||||
import LogShow from "./LogShow";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
|
||||
export default {
|
||||
@ -28,9 +27,9 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
logId: state => state.route.params.id,
|
||||
})
|
||||
logId() {
|
||||
return this.$route.params.id;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
|
@ -19,7 +19,6 @@
|
||||
<script>
|
||||
|
||||
import LogEdit from "./LogEdit";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from '../lib/Errors';
|
||||
|
||||
@ -37,9 +36,9 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
recipeId: state => state.route.params.recipeId,
|
||||
})
|
||||
recipeId() {
|
||||
return this.$route.params.recipeId;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
<script>
|
||||
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from "../lib/Errors";
|
||||
import LogEdit from "./LogEdit";
|
||||
@ -32,9 +31,9 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
logId: state => state.route.params.id,
|
||||
})
|
||||
logId() {
|
||||
return this.$route.params.id;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -5,19 +5,23 @@
|
||||
</h1>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Recipe</th>
|
||||
<th>Date</th>
|
||||
<th>Rating</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="l in logs" :key="l.id">
|
||||
<td> <router-link :to="{name: 'log', params: {id: l.id}}">{{l.recipe.name}}</router-link></td>
|
||||
<td><app-date-time :date-time="l.date" :show-time="false"></app-date-time> </td>
|
||||
<td><app-rating :value="l.rating" readonly></app-rating></td>
|
||||
<td>{{l.notes}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
@ -9,12 +9,15 @@
|
||||
</app-modal>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Note</th>
|
||||
<th>Date</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="n in notes" :key="n.id">
|
||||
<td>
|
||||
{{ n.content }}
|
||||
@ -28,6 +31,7 @@
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
@ -25,7 +25,6 @@
|
||||
<script>
|
||||
|
||||
import RecipeShow from "./RecipeShow";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
|
||||
export default {
|
||||
@ -37,13 +36,11 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
recipeId: state => state.route.params.id,
|
||||
routeQuery: state => state.route.query,
|
||||
scale: state => state.route.query.scale || null,
|
||||
system: state => state.route.query.system || null,
|
||||
unit: state => state.route.query.unit || null
|
||||
}),
|
||||
recipeId() { return this.$route.params.id },
|
||||
routeQuery() { return this.$route.query },
|
||||
scale() { return this.$route.query.scale || null },
|
||||
system() { return this.$route.query.system || null },
|
||||
unit() { return this.$route.query.unit || null },
|
||||
|
||||
isScaled() {
|
||||
return this.recipe.converted_scale !== null && this.recipe.converted_scale.length > 0 && this.recipe.converted_scale !== "1";
|
||||
|
@ -21,7 +21,6 @@
|
||||
<script>
|
||||
|
||||
import RecipeEdit from "./RecipeEdit";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from '../lib/Errors';
|
||||
|
||||
@ -34,9 +33,9 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
recipeId: state => state.route.params.id,
|
||||
})
|
||||
recipeId() {
|
||||
return this.$route.params.id;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
|
||||
|
||||
<table class="table is-fullwidth" :class="{ small: mediaQueries.touch }">
|
||||
<table class="table is-fullwidth" :class="{ small: isTouch }">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="h in tableHeader" :key="h.name">
|
||||
@ -91,9 +91,10 @@
|
||||
<script>
|
||||
|
||||
import api from "../lib/Api";
|
||||
import debounce from "lodash/debounce";
|
||||
import { mapMutations, mapState } from "vuex";
|
||||
import { mapWritableState, mapState } from "pinia";
|
||||
import AppLoading from "./AppLoading";
|
||||
import { useAppConfigStore } from "../stores/appConfig";
|
||||
import { useMediaQueryStore } from "../stores/mediaQuery";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@ -112,8 +113,9 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState([
|
||||
"mediaQueries"
|
||||
...mapState(useMediaQueryStore, { isTouch: store => store.touch }),
|
||||
...mapWritableState(useAppConfigStore, [
|
||||
"initialLoad"
|
||||
]),
|
||||
|
||||
search() {
|
||||
@ -174,10 +176,6 @@
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapMutations([
|
||||
"setInitialLoad"
|
||||
]),
|
||||
|
||||
buildQueryParams() {
|
||||
return {
|
||||
name: this.searchQuery.name,
|
||||
@ -302,7 +300,7 @@
|
||||
created() {
|
||||
this.$watch("search",
|
||||
() => {
|
||||
this.getList().then(() => this.setInitialLoad(true));
|
||||
this.getList().then(() => this.initialLoad = true);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
|
@ -24,9 +24,9 @@
|
||||
|
||||
<script>
|
||||
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from '../lib/Errors';
|
||||
import { mapActions, mapMutations, mapState } from "vuex";
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useTaskStore } from "../stores/task";
|
||||
|
||||
import TaskListMiniForm from "./TaskListMiniForm";
|
||||
import TaskListDropdownItem from "./TaskListDropdownItem";
|
||||
@ -48,7 +48,7 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState([
|
||||
...mapState(useTaskStore, [
|
||||
'taskLists',
|
||||
'currentTaskList'
|
||||
]),
|
||||
@ -62,14 +62,12 @@
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions([
|
||||
...mapActions(useTaskStore, [
|
||||
'refreshTaskLists',
|
||||
'createTaskList',
|
||||
'deleteTaskList',
|
||||
'deleteTaskItems',
|
||||
'completeTaskItems'
|
||||
]),
|
||||
...mapMutations([
|
||||
'completeTaskItems',
|
||||
'setCurrentTaskList'
|
||||
]),
|
||||
|
||||
|
@ -34,7 +34,7 @@
|
||||
<button type="submit" class="button is-primary" :disabled="!enableSubmit">Login</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-secondary" @click="showLogin = false">Cancel</button>
|
||||
<button type="button" class="button is-secondary" @click="showLogin = false">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
|
||||
<script>
|
||||
|
||||
import api from "../lib/Api";
|
||||
import { mapActions, mapState } from "vuex";
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useAppConfigStore } from "../stores/appConfig";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@ -61,7 +61,7 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState([
|
||||
...mapState(useAppConfigStore, [
|
||||
'loginMessage'
|
||||
]),
|
||||
enableSubmit() {
|
||||
@ -70,7 +70,7 @@
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions([
|
||||
...mapActions(useAppConfigStore, [
|
||||
'login'
|
||||
]),
|
||||
|
||||
@ -86,6 +86,7 @@
|
||||
this.loadResource(
|
||||
this.login(params)
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
if (data.success) {
|
||||
this.showLogin = false;
|
||||
}
|
||||
|
@ -1,54 +1,6 @@
|
||||
|
||||
import Vue from 'vue';
|
||||
import { mapActions, mapGetters, mapMutations, mapState } from 'vuex';
|
||||
import api from "../lib/Api";
|
||||
|
||||
Vue.mixin({
|
||||
data() {
|
||||
return {
|
||||
localLoadingCount: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
"isLoading",
|
||||
"isLoggedIn",
|
||||
"isAdmin"
|
||||
]),
|
||||
...mapState([
|
||||
"user"
|
||||
]),
|
||||
localLoading() {
|
||||
return this.localLoadingCount > 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'updateCurrentUser'
|
||||
]),
|
||||
...mapMutations([
|
||||
'setError',
|
||||
'setLoading'
|
||||
]),
|
||||
|
||||
loadResource(promise) {
|
||||
this.setLoading(true);
|
||||
this.localLoadingCount = this.localLoadingCount + 1;
|
||||
|
||||
return promise
|
||||
.catch(err => this.setError(err))
|
||||
.then(res => {
|
||||
this.setLoading(false);
|
||||
this.localLoadingCount = this.localLoadingCount - 1;
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
checkAuthentication() {
|
||||
return this.loadResource(this.updateCurrentUser());
|
||||
}
|
||||
}
|
||||
});
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import { useAppConfigStore } from "../stores/appConfig";
|
||||
|
||||
function clickStrikeClick(evt) {
|
||||
const isStrikable = el => el && el.tagName === "LI";
|
||||
@ -73,12 +25,59 @@ function clickStrikeClick(evt) {
|
||||
}
|
||||
}
|
||||
|
||||
Vue.directive('click-strike', {
|
||||
bind(el) {
|
||||
el.addEventListener("click", clickStrikeClick);
|
||||
},
|
||||
export function installMixins(app) {
|
||||
app.directive('click-strike', {
|
||||
beforeMount(el) {
|
||||
el.addEventListener("click", clickStrikeClick);
|
||||
},
|
||||
|
||||
unbind(el) {
|
||||
el.removeEventListener("click", clickStrikeClick);
|
||||
}
|
||||
});
|
||||
unmounted(el) {
|
||||
el.removeEventListener("click", clickStrikeClick);
|
||||
}
|
||||
});
|
||||
|
||||
app.mixin({
|
||||
data() {
|
||||
return {
|
||||
localLoadingCount: 0
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(useAppConfigStore, [
|
||||
"isLoading",
|
||||
"isLoggedIn",
|
||||
"isAdmin",
|
||||
"user"
|
||||
]),
|
||||
|
||||
localLoading() {
|
||||
return this.localLoadingCount > 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useAppConfigStore, [
|
||||
'setError',
|
||||
'setLoading',
|
||||
'updateCurrentUser'
|
||||
]),
|
||||
|
||||
loadResource(promise) {
|
||||
this.setLoading(true);
|
||||
this.localLoadingCount = this.localLoadingCount + 1;
|
||||
|
||||
return promise
|
||||
.catch(err => this.setError(err))
|
||||
.then(res => {
|
||||
this.setLoading(false);
|
||||
this.localLoadingCount = this.localLoadingCount - 1;
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
checkAuthentication() {
|
||||
return this.loadResource(this.updateCurrentUser());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
|
||||
import { useAppConfigStore } from '../stores/appConfig';
|
||||
|
||||
function trackInstall(worker, cb) {
|
||||
worker.addEventListener('statechange', function() {
|
||||
if (worker.state == 'installed') {
|
||||
@ -18,7 +20,9 @@ function trackActive(worker, cb) {
|
||||
export function swUpdate() {
|
||||
navigator.serviceWorker.getRegistration().then(reg => {
|
||||
if (reg && reg.waiting) {
|
||||
trackActive(reg.waiting, () => window.location.reload(true));
|
||||
trackActive(reg.waiting, () => {
|
||||
window.location.reload(true)
|
||||
});
|
||||
reg.waiting.postMessage("skipWaiting");
|
||||
} else {
|
||||
window.location.reload(true);
|
||||
@ -27,10 +31,17 @@ export function swUpdate() {
|
||||
|
||||
}
|
||||
|
||||
export function swInit(store) {
|
||||
export function swInit() {
|
||||
|
||||
const updateReady = () => store.commit("setUpdateAvailable", true);
|
||||
const clearUpdateReady = () => store.commit("setUpdateAvailable", false);
|
||||
const updateReady = () => {
|
||||
const store = useAppConfigStore();
|
||||
store.updateAvailable = true;
|
||||
};
|
||||
|
||||
const clearUpdateReady = () => {
|
||||
const store = useAppConfigStore();
|
||||
store.updateAvailable = false;
|
||||
}
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
@ -53,6 +64,4 @@ export function swInit(store) {
|
||||
console.log('Registration failed with ' + error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
54
app/javascript/packs/application.js
Normal file
54
app/javascript/packs/application.js
Normal file
@ -0,0 +1,54 @@
|
||||
import '../styles';
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { swInit } from "../lib/ServiceWorker";
|
||||
import config from '../lib/config';
|
||||
import { installMixins } from "../lib/GlobalMixins";
|
||||
import router from '../router';
|
||||
|
||||
import AppAutocomplete from "../components/AppAutocomplete";
|
||||
import AppConfirm from "../components/AppConfirm";
|
||||
import AppDateTime from "../components/AppDateTime";
|
||||
import AppDatePicker from "../components/AppDatePicker";
|
||||
import AppDropdown from "../components/AppDropdown";
|
||||
import AppExpandTransition from "../components/AppExpandTransition";
|
||||
import AppIcon from "../components/AppIcon";
|
||||
import AppIconicIcon from "../components/AppIconicIcon";
|
||||
import AppModal from "../components/AppModal";
|
||||
import AppNavbar from "../components/AppNavbar";
|
||||
import AppPager from "../components/AppPager";
|
||||
import AppRating from "../components/AppRating";
|
||||
import AppSearchText from "../components/AppSearchText";
|
||||
import AppTagEditor from "../components/AppTagEditor";
|
||||
import AppTextField from "../components/AppTextField";
|
||||
import AppValidationErrors from "../components/AppValidationErrors";
|
||||
|
||||
import App from '../components/App.vue'
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const app = createApp(App);
|
||||
const pinia = createPinia();
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
swInit();
|
||||
installMixins(app);
|
||||
|
||||
app.component("AppAutocomplete", AppAutocomplete);
|
||||
app.component("AppConfirm", AppConfirm);
|
||||
app.component("AppDateTime", AppDateTime);
|
||||
app.component("AppDatePicker", AppDatePicker);
|
||||
app.component("AppDropdown", AppDropdown);
|
||||
app.component("AppExpandTransition", AppExpandTransition);
|
||||
app.component("AppIcon", AppIcon);
|
||||
app.component("AppIconicIcon", AppIconicIcon);
|
||||
app.component("AppModal", AppModal);
|
||||
app.component("AppNavbar", AppNavbar);
|
||||
app.component("AppPager", AppPager);
|
||||
app.component("AppRating", AppRating);
|
||||
app.component("AppSearchText", AppSearchText);
|
||||
app.component("AppTagEditor", AppTagEditor);
|
||||
app.component("AppTextField", AppTextField);
|
||||
app.component("AppValidationErrors", AppValidationErrors);
|
||||
|
||||
app.mount('#app');
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
import The404Page from '../components/The404Page';
|
||||
import TheAboutPage from '../components/TheAboutPage';
|
||||
@ -28,26 +28,12 @@ import TheUserEditor from '../components/TheUserEditor';
|
||||
import TheAdminUserList from '../components/TheAdminUserList';
|
||||
import TheAdminUserEditor from '../components/TheAdminUserEditor';
|
||||
|
||||
import $store from '../store';
|
||||
import { useAppConfigStore } from "../stores/appConfig";
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
const router = new Router({
|
||||
routes: []
|
||||
});
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
if (to.meta.handleInitialLoad !== true && $store.state.initialLoad === false) {
|
||||
$store.commit("setInitialLoad", true);
|
||||
}
|
||||
|
||||
Vue.nextTick(() => {
|
||||
document.title = to.meta.title || 'Parsley';
|
||||
});
|
||||
});
|
||||
|
||||
router.addRoutes(
|
||||
[
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/recipes'
|
||||
@ -148,10 +134,10 @@ router.addRoutes(
|
||||
{
|
||||
path: "/logout",
|
||||
name: "logout",
|
||||
beforeEnter: (to, from, next) => {
|
||||
const $store = router.app.$store;
|
||||
$store.dispatch("logout")
|
||||
.then(() => next("/"));
|
||||
beforeEnter: async (to, from, next) => {
|
||||
const appConfig = useAppConfigStore();
|
||||
await appConfig.logout();
|
||||
return next("/");
|
||||
}
|
||||
},
|
||||
|
||||
@ -179,10 +165,22 @@ router.addRoutes(
|
||||
},
|
||||
|
||||
{
|
||||
path: '*',
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: The404Page
|
||||
}
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
const appConfigStore = useAppConfigStore();
|
||||
if (to.meta.handleInitialLoad !== true && appConfigStore.initialLoad === false) {
|
||||
appConfigStore.initialLoad = true;
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
document.title = to.meta.title || 'Parsley';
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
102
app/javascript/stores/appConfig.js
Normal file
102
app/javascript/stores/appConfig.js
Normal file
@ -0,0 +1,102 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute } from "vue-router";
|
||||
import api from "../lib/Api";
|
||||
|
||||
export const useAppConfigStore = defineStore('appConfig', () => {
|
||||
const authChecked = ref(false);
|
||||
const errorObject = ref(null);
|
||||
const initialLoad = ref(false);
|
||||
const loadingCount = ref(0);
|
||||
const loginMessage = ref(null);
|
||||
const updateAvailable = ref(false);
|
||||
const userObject = ref(null);
|
||||
const route = useRoute();
|
||||
|
||||
const isLoading = computed(() => {
|
||||
return loadingCount.value > 0;
|
||||
});
|
||||
|
||||
const isLoggedIn = computed(() => {
|
||||
return userObject.value !== null;
|
||||
});
|
||||
|
||||
const isAdmin = computed(() => {
|
||||
return userObject.value?.admin === true;
|
||||
});
|
||||
|
||||
const error = computed({
|
||||
get: () => errorObject.value,
|
||||
set: (val) => {
|
||||
console.log(val);
|
||||
errorObject.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
const user = computed({
|
||||
get: () => userObject.value,
|
||||
set: (val) => {
|
||||
userObject.value = val;
|
||||
authChecked.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
function setError(value) {
|
||||
error.value = value;
|
||||
}
|
||||
|
||||
function setLoading(value) {
|
||||
if (value) {
|
||||
loadingCount.value = loadingCount.value + 1;
|
||||
} else {
|
||||
loadingCount.value = loadingCount.value - 1;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCurrentUser() {
|
||||
user.value = await api.getCurrentUser();
|
||||
return user.value;
|
||||
}
|
||||
|
||||
function login(authData) {
|
||||
return api.postLogin(authData.username, authData.password)
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
user.value = data.user;
|
||||
loginMessage.value = null;
|
||||
} else {
|
||||
user.value = null;
|
||||
loginMessage.value = data.message;
|
||||
}
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
function logout() {
|
||||
return api.getLogout()
|
||||
.then(() => {
|
||||
user.value = null;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
authChecked,
|
||||
error,
|
||||
initialLoad,
|
||||
loadingCount,
|
||||
loginMessage,
|
||||
updateAvailable,
|
||||
user,
|
||||
route,
|
||||
|
||||
isAdmin,
|
||||
isLoading,
|
||||
isLoggedIn,
|
||||
|
||||
login,
|
||||
logout,
|
||||
setError,
|
||||
setLoading,
|
||||
updateCurrentUser
|
||||
};
|
||||
});
|
35
app/javascript/stores/mediaQuery.js
Normal file
35
app/javascript/stores/mediaQuery.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export const useMediaQueryStore = defineStore('mediaQuery', () => {
|
||||
// Hard coded values taken directly from Bulma css
|
||||
const mobileBp = 768;
|
||||
const desktopBp = 1024;
|
||||
const widscreenBp = 1216;
|
||||
const fullHdBp = 1408;
|
||||
|
||||
const mediaQueries = {
|
||||
mobile: `screen and (max-width: ${mobileBp}px)`,
|
||||
tablet: `screen and (min-width: ${mobileBp + 1}px)`,
|
||||
tabletOnly: `screen and (min-width: ${mobileBp + 1}px) and (max-width: ${desktopBp - 1}px)`,
|
||||
touch: `screen and (max-width: ${desktopBp - 1}px)`,
|
||||
desktop: `screen and (min-width: ${desktopBp}px)`,
|
||||
desktopOnly: `screen and (min-width: ${desktopBp}px) and (max-width: ${widscreenBp - 1}px)`,
|
||||
widescreen: `screen and (min-width: ${widscreenBp}px)`,
|
||||
widescreenOnly: `screen and (min-width: ${widscreenBp}px) and (max-width: ${fullHdBp - 1}px)`,
|
||||
fullhd: `screen and (min-width: ${fullHdBp}px)`
|
||||
};
|
||||
|
||||
const store = {};
|
||||
|
||||
for (let device in mediaQueries) {
|
||||
const query = window.matchMedia(mediaQueries[device]);
|
||||
store[device] = ref(query.matches);
|
||||
|
||||
query.onchange = (q) => {
|
||||
store[device].value = q.matches;
|
||||
};
|
||||
}
|
||||
|
||||
return store;
|
||||
});
|
36
app/javascript/stores/nutrient.js
Normal file
36
app/javascript/stores/nutrient.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export const useNutrientStore = defineStore('nutrient', () => {
|
||||
const nutrientList = ref({
|
||||
kcal: { label: "Calories", unit: "kcal" },
|
||||
protein: { label: "Protein", unit: "g" },
|
||||
lipids: { label: "Fat", unit: "g" },
|
||||
carbohydrates: { label: "Carbohydrates", unit: "g" },
|
||||
water: { label: "Water", unit: "g" },
|
||||
sugar: { label: "Sugar", unit: "g" },
|
||||
fiber: { label: "Fiber", unit: "g" },
|
||||
cholesterol: { label: "Cholesterol", unit: "mg" },
|
||||
sodium: { label: "Sodium", unit: "mg" },
|
||||
calcium: { label: "Calcium", unit: "mg" },
|
||||
iron: { label: "Iron", unit: "mg" },
|
||||
magnesium: { label: "Magnesium", unit: "mg" },
|
||||
phosphorus: { label: "Phosphorus", unit: "mg" },
|
||||
potassium: { label: "Potassium", unit: "mg" },
|
||||
zinc: { label: "Zinc", unit: "mg" },
|
||||
copper: { label: "Copper", unit: "mg" },
|
||||
manganese: { label: "Manganese", unit: "mg" },
|
||||
vit_a: { label: "Vitamin A", unit: "μg" },
|
||||
vit_b6: { label: "Vitamin B6", unit: "mg" },
|
||||
vit_b12: { label: "Vitamin B12", unit: "μg" },
|
||||
vit_c: { label: "Vitamin C", unit: "mg" },
|
||||
vit_d: { label: "Vitamin D", unit: "μg" },
|
||||
vit_e: { label: "Vitamin E", unit: "mg" },
|
||||
vit_k: { label: "Vitamin K", unit: "μg" },
|
||||
ash: { label: "ash", unit: "g" }
|
||||
});
|
||||
|
||||
return {
|
||||
nutrientList
|
||||
};
|
||||
});
|
114
app/javascript/stores/task.js
Normal file
114
app/javascript/stores/task.js
Normal file
@ -0,0 +1,114 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { useAppConfigStore } from "./appConfig";
|
||||
import api from "../lib/Api";
|
||||
import { createChannel } from "../lib/ActionCable";
|
||||
|
||||
export const useTaskStore = defineStore('task', () => {
|
||||
const taskLists = ref([]);
|
||||
const currentTaskList = ref(null);
|
||||
let taskChannel = null;
|
||||
|
||||
function replaceTaskList(list) {
|
||||
if (taskLists.value) {
|
||||
const listIdx = taskLists.value.findIndex(l => l.id === list.id);
|
||||
if (listIdx >= 0) {
|
||||
taskLists.value.splice(listIdx, 1, list);
|
||||
}
|
||||
if (currentTaskList.value && currentTaskList.value.id === list.id) {
|
||||
currentTaskList.value = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureTaskListChannel() {
|
||||
if (taskChannel === null) {
|
||||
taskChannel = createChannel(null, "TaskChannel", {
|
||||
received(data) {
|
||||
if (data && data.action === 'updated') {
|
||||
replaceTaskList(data.task_list);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function refreshTaskLists() {
|
||||
const cb = function(data) {
|
||||
taskLists.value = data || [];
|
||||
let ctl = null;
|
||||
|
||||
if (currentTaskList.value) {
|
||||
ctl = data.find(l => l.id === currentTaskList.value.id);
|
||||
}
|
||||
|
||||
ctl = ctl || data[0] || null;
|
||||
setCurrentTaskList(ctl);
|
||||
ensureTaskListChannel();
|
||||
};
|
||||
|
||||
return api.getTaskLists(cb)
|
||||
}
|
||||
|
||||
function ensureTaskLists() {
|
||||
const appConfig = useAppConfigStore();
|
||||
|
||||
if (appConfig.user && taskLists.value.length === 0) {
|
||||
return refreshTaskLists();
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
function setCurrentTaskList(taskList) {
|
||||
currentTaskList.value = taskList || null;
|
||||
}
|
||||
|
||||
async function createTaskList(newList) {
|
||||
currentTaskList.value = await api.postTaskList(newList);
|
||||
return refreshTaskLists();
|
||||
}
|
||||
|
||||
async function deleteTaskList(taskList) {
|
||||
await api.deleteTaskList(taskList);
|
||||
return refreshTaskLists();
|
||||
}
|
||||
|
||||
function createTaskItem(taskItem) {
|
||||
return api.postTaskItem(taskItem.task_list_id, taskItem)
|
||||
.then(data => {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
function updateTaskItem(taskItem) {
|
||||
return api.patchTaskItem(taskItem.task_list_id, taskItem)
|
||||
.then(data => {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
function deleteTaskItems(payload) {
|
||||
return api.deleteTaskItems(payload.taskList.id, payload.taskItems);
|
||||
}
|
||||
|
||||
function completeTaskItems(payload) {
|
||||
return api.completeTaskItems(payload.taskList.id, payload.taskItems, !payload.completed);
|
||||
}
|
||||
|
||||
return {
|
||||
currentTaskList,
|
||||
taskLists,
|
||||
|
||||
createTaskList,
|
||||
deleteTaskList,
|
||||
ensureTaskLists,
|
||||
refreshTaskLists,
|
||||
setCurrentTaskList,
|
||||
|
||||
createTaskItem,
|
||||
updateTaskItem,
|
||||
deleteTaskItems,
|
||||
completeTaskItems
|
||||
};
|
||||
});
|
@ -1,21 +1,25 @@
|
||||
@import "./variables";
|
||||
// coolors.co pallet
|
||||
$coolors-dark: rgba(29, 30, 24, 1);
|
||||
$coolors-blue: rgba(67, 127, 151, 1);
|
||||
$coolors-green: rgba(121, 167, 54, 1);
|
||||
$coolors-red: #ab4c34;
|
||||
$coolors-yellow: rgba(240, 162, 2, 1);
|
||||
|
||||
@import "~bulma/sass/utilities/_all";
|
||||
@import "~bulma/sass/base/_all";
|
||||
@import "~bulma/sass/components/dropdown";
|
||||
@import "~bulma/sass/components/navbar";
|
||||
@import "~bulma/sass/components/level";
|
||||
@import "~bulma/sass/components/message";
|
||||
@import "~bulma/sass/components/modal";
|
||||
@import "~bulma/sass/components/pagination";
|
||||
@import "~bulma/sass/components/panel";
|
||||
@import "~bulma/sass/elements/_all";
|
||||
@import "~bulma/sass/form/_all";
|
||||
@import "~bulma/sass/grid/columns";
|
||||
@import "~bulma/sass/layout/section";
|
||||
$family-serif: Georgia, "Times New Roman", Times, serif;
|
||||
|
||||
@import "./responsive_controls";
|
||||
@import "./wide_modal";
|
||||
@use "bulma/sass" as bulma with (
|
||||
$family-primary: $family-serif,
|
||||
$green: $coolors-green,
|
||||
$blue: $coolors-blue,
|
||||
$red: $coolors-red,
|
||||
$yellow: $coolors-yellow,
|
||||
$dark: $coolors-dark,
|
||||
$primary: $coolors-green,
|
||||
$modal-content-width: 750px
|
||||
);
|
||||
|
||||
// @import "./responsive_controls";
|
||||
// @import "./wide_modal";
|
||||
@import "./iconic";
|
||||
@import "./transitions";
|
||||
|
||||
@ -28,7 +32,7 @@ body {
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: $grey-dark;
|
||||
background-color: bulma.$grey-dark;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
@ -37,13 +41,13 @@ body {
|
||||
|
||||
.container {
|
||||
padding: 1rem;
|
||||
background-color: $white;
|
||||
background-color: bulma.$white;
|
||||
min-height: 75vh;
|
||||
}
|
||||
}
|
||||
|
||||
.title, .subtitle, .navbar, .button, .pagination, .modal-card-title, th {
|
||||
font-family: $family-sans-serif;
|
||||
font-family: bulma.$family-sans-serif;
|
||||
}
|
||||
|
||||
.pagination:not(:last-child) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
<%
|
||||
manifest_data = Webpacker::manifest.refresh
|
||||
manifest_timestamp = File.mtime(Webpacker::config.public_manifest_path).to_i
|
||||
manifest_data = Shakapacker::manifest.refresh
|
||||
manifest_timestamp = File.mtime(Shakapacker::config.public_manifest_path).to_i
|
||||
pack_assets = manifest_data.select { |name, asset| name !~ /\.map$/ && name != 'entrypoints' }.values
|
||||
%>
|
||||
|
||||
|
@ -17,6 +17,9 @@ FileUtils.chdir APP_ROOT do
|
||||
system! "gem install bundler --conservative"
|
||||
system("bundle check") || system!("bundle install")
|
||||
|
||||
# Install JavaScript dependencies
|
||||
system!("yarn install --no-immutable")
|
||||
|
||||
# puts "\n== Copying sample files =="
|
||||
# unless File.exist?("config/database.yml")
|
||||
# FileUtils.cp "config/database.yml.sample", "config/database.yml"
|
||||
|
@ -4,10 +4,10 @@ ENV["RAILS_ENV"] ||= "development"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
|
||||
|
||||
require "bundler/setup"
|
||||
require "webpacker"
|
||||
require "webpacker/webpack_runner"
|
||||
require "shakapacker"
|
||||
require "shakapacker/webpack_runner"
|
||||
|
||||
APP_ROOT = File.expand_path("..", __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
Webpacker::WebpackRunner.run(ARGV)
|
||||
Shakapacker::WebpackRunner.run(ARGV)
|
||||
end
|
@ -4,10 +4,10 @@ ENV["RAILS_ENV"] ||= "development"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
|
||||
|
||||
require "bundler/setup"
|
||||
require "webpacker"
|
||||
require "webpacker/dev_server_runner"
|
||||
require "shakapacker"
|
||||
require "shakapacker/dev_server_runner"
|
||||
|
||||
APP_ROOT = File.expand_path("..", __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
Webpacker::DevServerRunner.run(ARGV)
|
||||
Shakapacker::DevServerRunner.run(ARGV)
|
||||
end
|
19
bin/webpack
19
bin/webpack
@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
|
||||
ENV["NODE_ENV"] ||= "development"
|
||||
|
||||
require "pathname"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
require "webpacker"
|
||||
require "webpacker/webpack_runner"
|
||||
|
||||
APP_ROOT = File.expand_path("..", __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
Webpacker::WebpackRunner.run(ARGV)
|
||||
end
|
@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
|
||||
ENV["NODE_ENV"] ||= "development"
|
||||
|
||||
require "pathname"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
require "webpacker"
|
||||
require "webpacker/dev_server_runner"
|
||||
|
||||
APP_ROOT = File.expand_path("..", __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
Webpacker::DevServerRunner.run(ARGV)
|
||||
end
|
18
bin/yarn
18
bin/yarn
@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
APP_ROOT = File.expand_path("..", __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
yarn = ENV["PATH"].split(File::PATH_SEPARATOR).
|
||||
select { |dir| File.expand_path(dir) != __dir__ }.
|
||||
product(["yarn", "yarnpkg", "yarn.cmd", "yarn.ps1"]).
|
||||
map { |dir, file| File.expand_path(file, dir) }.
|
||||
find { |file| File.executable?(file) }
|
||||
|
||||
if yarn
|
||||
exec yarn, *ARGV
|
||||
else
|
||||
$stderr.puts "Yarn executable was not detected in the system."
|
||||
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
|
||||
exit 1
|
||||
end
|
||||
end
|
@ -1,15 +1,17 @@
|
||||
# Note: You must restart bin/webpacker-dev-server for changes to take effect
|
||||
# Note: You must restart bin/shakapacker-dev-server for changes to take effect
|
||||
# This file contains the defaults used by shakapacker.
|
||||
|
||||
default: &default
|
||||
source_path: app/javascript
|
||||
|
||||
# You can have a subdirectory of the source_path, like 'packs' (recommended).
|
||||
# Alternatively, you can use '/' to use the whole source_path directory.
|
||||
source_entry_path: /
|
||||
# Notice that this is a relative path to source_path
|
||||
source_entry_path: packs
|
||||
|
||||
# If nested_entries is true, then we'll pick up subdirectories within the source_entry_path.
|
||||
# You cannot set this option to true if you set source_entry_path to '/'
|
||||
nested_entries: false
|
||||
nested_entries: true
|
||||
|
||||
# While using a File-System-based automated bundle generation feature, miscellaneous warnings suggesting css order
|
||||
# conflicts may arise due to the mini-css-extract-plugin. For projects where css ordering has been mitigated through
|
||||
@ -19,10 +21,10 @@ default: &default
|
||||
|
||||
public_root_path: public
|
||||
public_output_path: packs
|
||||
cache_path: tmp/webpacker
|
||||
cache_path: tmp/shakapacker
|
||||
webpack_compile_output: true
|
||||
# See https://github.com/shakacode/shakapacker#deployment
|
||||
webpacker_precompile: true
|
||||
shakapacker_precompile: true
|
||||
|
||||
# Location for manifest.json, defaults to {public_output_path}/manifest.json if unset
|
||||
# manifest_path: public/packs/manifest.json
|
||||
@ -35,48 +37,63 @@ default: &default
|
||||
cache_manifest: false
|
||||
|
||||
# Select loader to use, available options are 'babel' (default), 'swc' or 'esbuild'
|
||||
webpack_loader: 'babel'
|
||||
webpack_loader: 'swc'
|
||||
|
||||
# Set to true to enable check for matching versions of shakapacker gem and NPM package - will raise an error if there is a mismatch or wildcard versioning is used
|
||||
ensure_consistent_versioning: false
|
||||
# Raises an error if there is a mismatch in the shakapacker gem and npm package being used
|
||||
ensure_consistent_versioning: true
|
||||
|
||||
# Select whether the compiler will use SHA digest ('digest' option) or most most recent modified timestamp ('mtime') to determine freshness
|
||||
compiler_strategy: digest
|
||||
|
||||
# Select whether the compiler will always use a content hash and not just in production
|
||||
# Don't use contentHash except for production for performance
|
||||
# https://webpack.js.org/guides/build-performance/#avoid-production-specific-tooling
|
||||
useContentHash: false
|
||||
|
||||
# Setting the asset host here will override Rails.application.config.asset_host.
|
||||
# Here, you can set different asset_host per environment. Note that
|
||||
# SHAKAPACKER_ASSET_HOST will override both configurations.
|
||||
# asset_host: custom-path
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
compile: true
|
||||
compiler_strategy: mtime
|
||||
|
||||
# Reference: https://webpack.js.org/configuration/dev-server/
|
||||
# Keys not described there are documented inline and in https://github.com/shakacode/shakapacker/
|
||||
dev_server:
|
||||
https: false
|
||||
# For running dev server with https, set `server: https`.
|
||||
# server: https
|
||||
|
||||
host: localhost
|
||||
port: 3035
|
||||
# Hot Module Replacement updates modules while the application is running without a full reload
|
||||
# Used instead of the `hot` key in https://webpack.js.org/configuration/dev-server/#devserverhot
|
||||
hmr: false
|
||||
# If HMR is on, CSS will by inlined by delivering it as part of the script payload via style-loader. Be sure
|
||||
# that you add style-loader to your project dependencies.
|
||||
#
|
||||
# If you want to instead deliver CSS via <link> with the mini-extract-css-plugin, set inline_css to false.
|
||||
# If you want to instead deliver CSS via <link> with the mini-css-extract-plugin, set inline_css to false.
|
||||
# In that case, style-loader is not needed as a dependency.
|
||||
#
|
||||
# mini-extract-css-plugin is a required dependency in both cases.
|
||||
# mini-css-extract-plugin is a required dependency in both cases.
|
||||
inline_css: true
|
||||
# Defaults to the inverse of hmr. Uncomment to manually set this.
|
||||
# live_reload: true
|
||||
live_reload: false
|
||||
client:
|
||||
# Should we show a full-screen overlay in the browser when there are compiler errors or warnings?
|
||||
overlay: true
|
||||
# May also be a string
|
||||
# webSocketURL:
|
||||
# hostname: "0.0.0.0"
|
||||
# pathname: "/ws"
|
||||
# hostname: '0.0.0.0'
|
||||
# pathname: '/ws'
|
||||
# port: 8080
|
||||
# Should we use gzip compression?
|
||||
compress: true
|
||||
# Note that apps that do not check the host are vulnerable to DNS rebinding attacks
|
||||
allowed_hosts: "all"
|
||||
allowed_hosts: 'auto'
|
||||
# Shows progress and colorizes output of bin/shakapacker[-dev-server]
|
||||
pretty: true
|
||||
headers:
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
@ -91,17 +108,14 @@ test:
|
||||
# Compile test packs to a separate directory
|
||||
public_output_path: packs-test
|
||||
|
||||
production: &production
|
||||
production:
|
||||
<<: *default
|
||||
|
||||
# Production depends on precompilation of packs prior to booting for performance.
|
||||
compile: false
|
||||
|
||||
# Use content hash for naming assets. Cannot be overridden by for production.
|
||||
useContentHash: true
|
||||
|
||||
# Cache manifest.json for performance
|
||||
cache_manifest: true
|
||||
|
||||
docker:
|
||||
<<: *production
|
||||
|
||||
beta:
|
||||
<<: *production
|
@ -13,9 +13,11 @@ const iriElementsAndProperties = {
|
||||
};
|
||||
|
||||
module.exports = function(content) {
|
||||
|
||||
this.cacheable && this.cacheable();
|
||||
|
||||
const $ = cheerio.load(content, {
|
||||
xmlMode: true,
|
||||
lowerCaseTags: false
|
||||
});
|
||||
|
||||
@ -71,6 +73,7 @@ module.exports = function(content) {
|
||||
const svgMarkup = $("svg").html();
|
||||
|
||||
|
||||
|
||||
return `module.exports = ${JSON.stringify({
|
||||
attributes: attributes,
|
||||
content: svgMarkup,
|
||||
|
@ -2,4 +2,4 @@ module.exports = {
|
||||
resolve: {
|
||||
extensions: ['.css', '.scss']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,9 @@ module.exports = {
|
||||
{
|
||||
test: /\/iconic\/svg\/.*\.svg$/,
|
||||
use: [
|
||||
'vue-loader',
|
||||
path.join(__dirname, '../loaders/iconic_svg_loader.js')
|
||||
{ loader: path.join(__dirname, '../loaders/iconic_svg_loader.js') }
|
||||
],
|
||||
type: 'asset/source'
|
||||
type: 'javascript/auto'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1,16 +1,25 @@
|
||||
const { env } = require('shakapacker')
|
||||
const { DefinePlugin } = require('webpack')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [new VueLoaderPlugin()],
|
||||
resolve: {
|
||||
extensions: ['.vue']
|
||||
}
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new DefinePlugin({
|
||||
__VUE_OPTIONS_API__: true,
|
||||
__VUE_PROD_DEVTOOLS__: env.isDevelopment,
|
||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: env.isDevelopment
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.vue']
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,34 @@
|
||||
const { merge, mergeWithRules, webpackConfig } = require('shakapacker')
|
||||
const cssConfig = require('./rules/css');
|
||||
const vueConfig = require('./rules/vue');
|
||||
const svgConfig = require('./rules/svg');
|
||||
// See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.
|
||||
const { generateWebpackConfig, merge, mergeWithRules } = require('shakapacker')
|
||||
const cssConfig = require('./rules/css')
|
||||
const svgConfig = require('./rules/svg')
|
||||
const vueConfig = require('./rules/vue')
|
||||
|
||||
const webpackConfig = generateWebpackConfig()
|
||||
|
||||
let conf = merge(vueConfig, svgConfig);
|
||||
conf = merge(cssConfig, conf);
|
||||
conf = merge(conf, webpackConfig);
|
||||
|
||||
const updateFileLoaderConf = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
type: 'asset/resource',
|
||||
// This version does not include svg. See https://github.com/shakacode/shakapacker/blob/v6.2.0/package/rules/file.js
|
||||
test: /\.(bmp|gif|jpe?g|png|tiff|ico|avif|webp|eot|otf|ttf|woff|woff2)$/
|
||||
}
|
||||
]
|
||||
}
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
type: 'asset/resource',
|
||||
// This version does not include svg. See https://github.com/shakacode/shakapacker/blob/v6.2.0/package/rules/file.js
|
||||
test: /\.(bmp|gif|jpe?g|png|tiff|ico|avif|webp|eot|otf|ttf|woff|woff2)$/
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
conf = mergeWithRules({
|
||||
module: {
|
||||
rules: {
|
||||
type: "match",
|
||||
test: "replace"
|
||||
}
|
||||
module: {
|
||||
rules: {
|
||||
type: "match",
|
||||
test: "replace"
|
||||
}
|
||||
}
|
||||
})(conf, updateFileLoaderConf);
|
||||
|
||||
|
||||
module.exports = conf
|
||||
|
24
index.html
24
index.html
@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name ="theme-color" content="rgba(121, 167, 54, 1)">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<title>Parsley</title>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="app" data-url="http://localhost:3000/">
|
||||
<div id="app-placeholder">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/packs/application-3067a90281a92d120ca6.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,54 +0,0 @@
|
||||
namespace :dev do
|
||||
|
||||
desc 'Run both rails server and webpack dev server'
|
||||
task :run do
|
||||
|
||||
rails_pid = fork do
|
||||
exec 'rails s'
|
||||
end
|
||||
|
||||
webpack_pid = fork do
|
||||
exec 'bin/webpack-dev-server'
|
||||
end
|
||||
|
||||
running = true
|
||||
shutdown = false
|
||||
shutdown_start = nil
|
||||
|
||||
Signal.trap('SIGINT') do
|
||||
shutdown = true
|
||||
end
|
||||
|
||||
while running
|
||||
rails_check ||= Process.waitpid(rails_pid, Process::WNOHANG)
|
||||
webpack_check ||= Process.waitpid(webpack_pid, Process::WNOHANG)
|
||||
running = rails_check.nil? || webpack_check.nil?
|
||||
|
||||
if shutdown
|
||||
if shutdown_start.nil?
|
||||
puts "Shutting down..."
|
||||
shutdown_start = Time.now
|
||||
#Process.kill("SIGINT", rails_pid) rescue Errno::ESRCH
|
||||
#Process.kill("SIGINT", webpack_pid) rescue Errno::ESRCH
|
||||
end
|
||||
|
||||
if (Time.now - shutdown_start) > 5
|
||||
if rails_check.nil?
|
||||
puts "Force killing rails..."
|
||||
Process.kill("KILL", rails_pid)
|
||||
end
|
||||
|
||||
if webpack_check.nil?
|
||||
puts "Force killing webpack..."
|
||||
Process.kill("KILL", webpack_pid)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
sleep 0.25
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
80
package.json
80
package.json
@ -1,44 +1,11 @@
|
||||
{
|
||||
"name": "parsley",
|
||||
"packageManager": "yarn@4.5.0",
|
||||
"name": "app",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7",
|
||||
"@babel/plugin-transform-runtime": "7",
|
||||
"@babel/preset-env": "7",
|
||||
"@babel/runtime": "7",
|
||||
"@rails/actioncable": "^7.0.4",
|
||||
"@tweenjs/tween.js": "^18.6.4",
|
||||
"autosize": "^5.0.2",
|
||||
"babel-loader": "8",
|
||||
"bulma": "0.9.4",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"compression-webpack-plugin": "9",
|
||||
"css-loader": "^6.7.2",
|
||||
"css-minimizer-webpack-plugin": "^4.2.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mini-css-extract-plugin": "^2.7.1",
|
||||
"sass": "^1.56.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"shakapacker": "6.5.4",
|
||||
"style-loader": "^3.3.1",
|
||||
"svg-loader": "^0.0.2",
|
||||
"terser-webpack-plugin": "5",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue": "^2.6.14",
|
||||
"vue-loader": "^15.10.1",
|
||||
"vue-progressbar": "^0.7.5",
|
||||
"vue-resize": "^1.0.1",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
"webpack": "5",
|
||||
"webpack-assets-manifest": "5",
|
||||
"webpack-cli": "4",
|
||||
"webpack-dev-server": "^4.11.1",
|
||||
"webpack-merge": "5"
|
||||
},
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"console": "node"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"./node_modules/shakapacker/package/babel/preset.js"
|
||||
@ -47,5 +14,40 @@
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
"dependencies": {
|
||||
"@babel/core": "7",
|
||||
"@babel/plugin-transform-runtime": "7",
|
||||
"@babel/preset-env": "7",
|
||||
"@babel/runtime": "7",
|
||||
"@rails/actioncable": "7.2.100",
|
||||
"@swc/core": "1.7.26",
|
||||
"@tweenjs/tween.js": "^25.0.0",
|
||||
"@types/babel__core": "7",
|
||||
"@types/webpack": "5",
|
||||
"babel-loader": "8",
|
||||
"bulma": "^1.0.2",
|
||||
"cheerio": "^1.0.0",
|
||||
"compression-webpack-plugin": "9",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mini-css-extract-plugin": "^2.9.1",
|
||||
"pinia": "^2.2.2",
|
||||
"sass": "~1.78.0",
|
||||
"sass-loader": "^16.0.2",
|
||||
"shakapacker": "8.0.2",
|
||||
"style-loader": "^4.0.0",
|
||||
"swc-loader": "^0.2.6",
|
||||
"terser-webpack-plugin": "5",
|
||||
"vue": "^3.5.10",
|
||||
"vue-loader": "^17.4.2",
|
||||
"vue-router": "^4.4.5",
|
||||
"webpack": "5",
|
||||
"webpack-assets-manifest": "5",
|
||||
"webpack-cli": "4",
|
||||
"webpack-merge": "5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-dev-server": "4"
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('postcss-import'),
|
||||
require('postcss-flexbugs-fixes'),
|
||||
require('postcss-preset-env')({
|
||||
autoprefixer: {
|
||||
flexbox: 'no-2009'
|
||||
},
|
||||
stage: 3
|
||||
})
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user